Files
steampipe/connection_config/parser.go

187 lines
5.1 KiB
Go

package connection_config
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/turbot/steampipe-plugin-sdk/plugin"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/ociinstaller"
"github.com/turbot/steampipe/schema"
)
const configExtension = ".spc"
func Load() (*ConnectionConfigMap, error) {
return loadConfig(constants.ConfigDir())
}
func loadConfig(configFolder string) (result *ConnectionConfigMap, err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
result = newConfigMap()
// get all the config files in the directory
configPaths, err := getConfigFilePaths(configFolder)
if err != nil {
log.Printf("[WARN] loadConfig: failed to get config file paths: %v\n", err)
return nil, err
}
if len(configPaths) == 0 {
log.Println("[DEBUG] loadConfig: 0 config file paths returned")
return &ConnectionConfigMap{}, nil
}
fileData, diags := loadFileData(configPaths)
if diags.HasErrors() {
log.Printf("[WARN] loadConfig: failed to get config file paths: %v\n", err)
return nil, plugin.DiagsToError("failed to load all config files", diags)
}
body, diags := parseConfigs(fileData)
if diags.HasErrors() {
return nil, plugin.DiagsToError("failed to load all config files", diags)
}
// do a partial decode
content, _, moreDiags := body.PartialContent(configSchema)
if moreDiags.HasErrors() {
diags = append(diags, moreDiags...)
return nil, plugin.DiagsToError("failed to decode config", diags)
}
for _, block := range content.Blocks {
switch block.Type {
case "connection":
connection, moreDiags := parseConnection(block, fileData)
if moreDiags.HasErrors() {
diags = append(diags, moreDiags...)
continue
}
_, alreadyThere := result.Connections[connection.Name]
if alreadyThere {
return nil, fmt.Errorf("duplicate connection name: '%s' in '%s'", connection.Name, block.TypeRange.Filename)
}
if !schema.IsSchemaNameValid(connection.Name) {
return nil, fmt.Errorf("invalid connection name: '%s' in '%s'", connection.Name, block.TypeRange.Filename)
}
result.Connections[connection.Name] = connection
}
}
if diags.HasErrors() {
return nil, plugin.DiagsToError("failed to load config", diags)
return nil, fmt.Errorf(diags.Error())
}
return result, nil
}
func loadFileData(configPaths []string) (map[string][]byte, hcl.Diagnostics) {
var diags hcl.Diagnostics
var fileData = map[string][]byte{}
for _, configPath := range configPaths {
data, err := ioutil.ReadFile(configPath)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("failed to read config file %s", configPath),
Detail: err.Error()})
continue
}
fileData[configPath] = data
}
return fileData, diags
}
func parseConfigs(fileData map[string][]byte) (hcl.Body, hcl.Diagnostics) {
var parsedConfigFiles []*hcl.File
var diags hcl.Diagnostics
parser := hclparse.NewParser()
for configPath, data := range fileData {
file, moreDiags := parser.ParseHCL(data, configPath)
if moreDiags.HasErrors() {
diags = append(diags, moreDiags...)
continue
}
parsedConfigFiles = append(parsedConfigFiles, file)
}
return hcl.MergeFiles(parsedConfigFiles), diags
}
func parseConnection(block *hcl.Block, fileData map[string][]byte) (*Connection, hcl.Diagnostics) {
connectionBlock, rest, diags := block.Body.PartialContent(connectionSchema)
if diags.HasErrors() {
return nil, diags
}
// get connection name
connectionName := block.Labels[0]
var plugin string
diags = gohcl.DecodeExpression(connectionBlock.Attributes["plugin"].Expr, nil, &plugin)
if diags.HasErrors() {
return nil, diags
}
connectionPlugin := ociinstaller.NewSteampipeImageRef(plugin).DisplayImageRef()
// now build a string containing the hcl for all other conneciton config properties
restBody := rest.(*hclsyntax.Body)
var configProperties []string
for name, a := range restBody.Attributes {
// if this attribute does not appear in connectionBlock, load the hcl string
if _, ok := connectionBlock.Attributes[name]; !ok {
configProperties = append(configProperties, string(a.SrcRange.SliceBytes(fileData[a.SrcRange.Filename])))
}
}
connectionConfig := strings.Join(configProperties, "\n")
connection := &Connection{
Name: connectionName,
Plugin: connectionPlugin,
Config: connectionConfig,
}
return connection, nil
}
func getConfigFilePaths(configFolder string) ([]string, error) {
// check folder exists - if not just return empty config
if _, err := os.Stat(configFolder); os.IsNotExist(err) {
return nil, nil
}
entries, err := ioutil.ReadDir(configFolder)
if err != nil {
return nil, fmt.Errorf("failed to read config folder %s: %v", configFolder, err)
}
matches := []string{}
for _, entry := range entries {
if filepath.Ext(entry.Name()) == configExtension {
matches = append(matches, filepath.Join(configFolder, entry.Name()))
}
}
return matches, nil
}