From e9a899a3cfe41a622202808a0241b7f40b54d338 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 26 Nov 2024 01:18:50 +0600 Subject: [PATCH] feat(misconf): log causes of HCL file parsing errors (#7634) Signed-off-by: nikpivkin Co-authored-by: Simar Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com> --- pkg/iac/scanners/terraform/parser/parser.go | 61 +++++++++++++++++-- .../scanners/terraform/parser/parser_test.go | 21 +++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index 866835d5f8a5..48ed799155ea 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -1,8 +1,10 @@ package parser import ( + "bufio" "context" "errors" + "fmt" "io" "io/fs" "os" @@ -166,18 +168,69 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { } sort.Strings(paths) for _, path := range paths { - if err := p.ParseFile(ctx, path); err != nil { - if p.stopOnHCLError { + var err error + if err = p.ParseFile(ctx, path); err == nil { + continue + } + + if p.stopOnHCLError { + return err + } + var diags hcl.Diagnostics + if errors.As(err, &diags) { + errc := p.showParseErrors(p.moduleFS, path, diags) + if errc == nil { + continue + } + p.logger.Error("Failed to get the causes of the parsing error", log.Err(errc)) + } + p.logger.Error("Error parsing file", log.FilePath(path), log.Err(err)) + continue + } + + return nil +} + +func (p *Parser) showParseErrors(fsys fs.FS, filePath string, diags hcl.Diagnostics) error { + file, err := fsys.Open(filePath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + defer file.Close() + + for _, diag := range diags { + if subj := diag.Subject; subj != nil { + lines, err := readLinesFromFile(file, subj.Start.Line, subj.End.Line) + if err != nil { return err } - p.logger.Error("Error parsing file", log.FilePath(path), log.Err(err)) - continue + + cause := strings.Join(lines, "\n") + p.logger.Error("Error parsing file", log.FilePath(filePath), + log.String("cause", cause), log.Err(diag)) } } return nil } +func readLinesFromFile(f io.Reader, from, to int) ([]string, error) { + scanner := bufio.NewScanner(f) + rawLines := make([]string, 0, to-from+1) + + for lineNum := 0; scanner.Scan() && lineNum < to; lineNum++ { + if lineNum >= from-1 { + rawLines = append(rawLines, scanner.Text()) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan file: %w", err) + } + + return rawLines, nil +} + var ErrNoFiles = errors.New("no files found") func (p *Parser) Load(ctx context.Context) (*evaluator, error) { diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 926eb353af26..49976bd8bb55 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -2010,6 +2010,27 @@ variable "baz" {} assert.Contains(t, buf.String(), "variables=\"foo\"") } +func TestLogParseErrors(t *testing.T) { + var buf bytes.Buffer + slog.SetDefault(slog.New(log.NewHandler(&buf, nil))) + + src := `resource "aws-s3-bucket" "name" { + bucket = < +}` + + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(src), + }, + } + + parser := New(fsys, "") + err := parser.ParseFS(context.TODO(), ".") + require.NoError(t, err) + + assert.Contains(t, buf.String(), `cause=" bucket = <"`) +} + func Test_PassingNullToChildModule_DoesNotEraseType(t *testing.T) { tests := []struct { name string