Skip to content

Commit

Permalink
Implement module validation on save
Browse files Browse the repository at this point in the history
  • Loading branch information
appilon committed Dec 4, 2020
1 parent 954e33f commit 5254615
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 28 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ require (
github.com/creachadair/jrpc2 v0.11.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gammazero/workerpool v1.0.0
github.com/google/go-cmp v0.5.1
github.com/google/go-cmp v0.5.2
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/hcl-lang v0.0.0-20201116081236-948e43712a65
github.com/hashicorp/hcl/v2 v2.6.0
github.com/hashicorp/terraform-exec v0.11.1-0.20201007122305-ea2094d52cb5
github.com/hashicorp/terraform-exec v0.11.1-0.20201201215927-6e0fd8a457b8
github.com/hashicorp/terraform-json v0.6.0
github.com/hashicorp/terraform-schema v0.0.0-20201110191417-e2e5d08913c4
github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5
Expand All @@ -23,6 +23,6 @@ require (
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
github.com/spf13/afero v1.3.2
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.6.1
github.com/vektra/mockery/v2 v2.3.0
)
12 changes: 10 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
Expand Down Expand Up @@ -194,8 +196,10 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/terraform-config-inspect v0.0.0-20201102131242-0c45ba392e51 h1:SEGO1vz/pFLfKy4QpABIMCe7wffmtsOiWO4yc1E87cU=
github.com/hashicorp/terraform-config-inspect v0.0.0-20201102131242-0c45ba392e51/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs=
github.com/hashicorp/terraform-exec v0.11.1-0.20201007122305-ea2094d52cb5 h1:P+lBGicJEG3ijvOrDdQf/Oo8UrG4QAJbdY3g9OGBnr0=
github.com/hashicorp/terraform-exec v0.11.1-0.20201007122305-ea2094d52cb5/go.mod h1:eQdBvA0Xr/ZJNilY8TzrtePLSqLyexk9PSwVwzzHTjY=
github.com/hashicorp/terraform-exec v0.11.1-0.20201126033416-f9d65bf2bca2 h1:WNNF6oK2BXG0nGalTXDjP2XwdWxARRtGuN2Ih1ECIn8=
github.com/hashicorp/terraform-exec v0.11.1-0.20201126033416-f9d65bf2bca2/go.mod h1:162lr1MQN7XkrqevlStXPonCGMP5yvyG9zWnlmGdQLI=
github.com/hashicorp/terraform-exec v0.11.1-0.20201201215927-6e0fd8a457b8 h1:sJXjDnxrrpmmJcAiPqX1HL7uksBgVXjY+3VX6k09zkA=
github.com/hashicorp/terraform-exec v0.11.1-0.20201201215927-6e0fd8a457b8/go.mod h1:162lr1MQN7XkrqevlStXPonCGMP5yvyG9zWnlmGdQLI=
github.com/hashicorp/terraform-json v0.5.0 h1:7TV3/F3y7QVSuN4r9BEXqnWqrAyeOtON8f0wvREtyzs=
github.com/hashicorp/terraform-json v0.5.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU=
github.com/hashicorp/terraform-json v0.6.0 h1:nMTj4t9ysC7xJ72rvVsDqhUccvbUINrjhPqafeUeREk=
Expand Down Expand Up @@ -349,6 +353,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
Expand Down Expand Up @@ -549,6 +555,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
13 changes: 13 additions & 0 deletions internal/terraform/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ func (e *Executor) Format(ctx context.Context, input []byte) ([]byte, error) {
return buf.Bytes(), e.contextfulError(ctx, "Format", err)
}

func (e *Executor) Validate(ctx context.Context) ([]tfexec.Diagnostic, error) {
ctx, cancel := e.withTimeout(ctx)
defer cancel()
err := e.setLogPath("Validate")
if err != nil {
return []tfexec.Diagnostic{}, err
}

validation, err := e.tf.Validate(ctx)

return validation.Diagnostics, e.contextfulError(ctx, "Validate", err)
}

func (e *Executor) Version(ctx context.Context) (*version.Version, error) {
ctx, cancel := e.withTimeout(ctx)
defer cancel()
Expand Down
27 changes: 26 additions & 1 deletion internal/terraform/exec/mock/executor.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions internal/terraform/exec/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-exec/tfexec"
tfjson "github.com/hashicorp/terraform-json"
)

Expand All @@ -24,6 +25,7 @@ type TerraformExecutor interface {
GetExecPath() string
Init(ctx context.Context) error
Format(ctx context.Context, input []byte) ([]byte, error)
Validate(ctx context.Context) ([]tfexec.Diagnostic, error)
Version(ctx context.Context) (*version.Version, error)
ProviderSchemas(ctx context.Context) (*tfjson.ProviderSchemas, error)
}
61 changes: 61 additions & 0 deletions internal/terraform/rootmodule/root_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,67 @@ func (rm *rootModule) ExecuteTerraformInit(ctx context.Context) error {
return rm.tfExec.Init(ctx)
}

func (rm *rootModule) ExecuteTerraformValidate(ctx context.Context) (map[string]hcl.Diagnostics, error) {
diagsMap := make(map[string]hcl.Diagnostics)

if !rm.IsTerraformAvailable() {
if err := rm.discoverTerraformExecutor(ctx); err != nil {
return diagsMap, err
}
}

if !rm.IsParsed() {
if err := rm.ParseFiles(); err != nil {
return diagsMap, err
}
}

// an entry for each file should exist, even if there are no diags
for filename := range rm.pFilesMap {
diagsMap[filename] = make(hcl.Diagnostics, 0)
}

validationDiags, err := rm.tfExec.Validate(ctx)
if err != nil {
return diagsMap, err
}

// tfexec.Diagnostic is a conversion of an internal diag to terraform core,
// tfdiags, which is effectively based on hcl.Diagnostic.
// This process is really just converting it back to hcl.Diagnotic
// since it is the defacto diagnostic type for our codebase currently
// https://github.com/hashicorp/terraform/blob/ae025248cc0712bf53c675dc2fe77af4276dd5cc/command/validate.go#L138
for _, d := range validationDiags {
// the diagnostic must be tied to a file to exist in the map
if d.Range == nil || d.Range.Filename == "" {
continue
}

diags := diagsMap[d.Range.Filename]

var severity hcl.DiagnosticSeverity
if d.Severity == "error" {
severity = hcl.DiagError
} else if d.Severity == "warning" {
severity = hcl.DiagWarning
}

diags = append(diags, &hcl.Diagnostic{
Severity: severity,
Summary: d.Summary,
Detail: d.Detail,
Subject: &hcl.Range{
Filename: d.Range.Filename,
Start: hcl.Pos(d.Range.Start),
End: hcl.Pos(d.Range.End),
},
})
diagsMap[d.Range.Filename] = diags
}

return diagsMap, nil
}

func (rm *rootModule) discoverTerraformVersion(ctx context.Context) error {
if rm.tfExec == nil {
return errors.New("no terraform executor - unable to read version")
Expand Down
2 changes: 2 additions & 0 deletions internal/terraform/rootmodule/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ type RootModule interface {
HasTerraformDiscoveryFinished() bool
IsTerraformAvailable() bool
ExecuteTerraformInit(ctx context.Context) error
ExecuteTerraformValidate(ctx context.Context) (map[string]hcl.Diagnostics, error)
Modules() []ModuleRecord
WasInitialized() (bool, error)
}

type RootModuleFactory func(context.Context, string) (*rootModule, error)
Expand Down
55 changes: 40 additions & 15 deletions langserver/diagnostics/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,28 @@ import (
type diagContext struct {
ctx context.Context
uri lsp.DocumentURI
diags hcl.Diagnostics
source string
diags []lsp.Diagnostic
}

// Notifier is a type responsible for queueing hcl diagnostics to be converted
// and sent to the client
type Notifier struct {
logger *log.Logger
sessCtx context.Context
diags chan diagContext
diagsCache map[string]map[string][]lsp.Diagnostic
closeDiagsOnce sync.Once
}

func NewNotifier(sessCtx context.Context, logger *log.Logger) *Notifier {
diags := make(chan diagContext, 50)
go notify(diags, logger)
return &Notifier{sessCtx: sessCtx, diags: diags}
n := &Notifier{logger: logger, sessCtx: sessCtx, diags: make(chan diagContext, 50), diagsCache: make(map[string]map[string][]lsp.Diagnostic)}
go n.notify()
return n
}

// Publish accepts a map of diagnostics per file and queues them for publishing
func (n *Notifier) Publish(ctx context.Context, rmDir string, diags map[string]hcl.Diagnostics, source string) {
func (n *Notifier) Publish(ctx context.Context, dir string, diags map[string][]lsp.Diagnostic, source string) {
select {
case <-n.sessCtx.Done():
n.closeDiagsOnce.Do(func() {
Expand All @@ -45,22 +47,45 @@ func (n *Notifier) Publish(ctx context.Context, rmDir string, diags map[string]h
default:
}

if source == "" {
source = "Terraform"
}

for path, ds := range diags {
n.diags <- diagContext{ctx: ctx, diags: ds, source: source, uri: lsp.DocumentURI(uri.FromPath(filepath.Join(rmDir, path)))}
for filename, ds := range diags {
n.diags <- diagContext{ctx: ctx, source: source, diags: ds, uri: lsp.DocumentURI(uri.FromPath(filepath.Join(dir, filename)))}
}
}

func notify(diags <-chan diagContext, logger *log.Logger) {
for d := range diags {
func (n *Notifier) notify() {
for d := range n.diags {
if err := jrpc2.PushNotify(d.ctx, "textDocument/publishDiagnostics", lsp.PublishDiagnosticsParams{
URI: d.uri,
Diagnostics: ilsp.HCLDiagsToLSP(d.diags, d.source),
Diagnostics: n.mergeDiags(string(d.uri), d.source, d.diags),
}); err != nil {
logger.Printf("Error pushing diagnostics: %s", err)
n.logger.Printf("Error pushing diagnostics: %s", err)
}
}
}

// mergeDiags will return all diags from all cached sources for a given uri.
// the passed diags overwrites the cached entry for the passed source key
// even if empty
func (n *Notifier) mergeDiags(uri string, source string, diags []lsp.Diagnostic) []lsp.Diagnostic {
fileDiags, ok := n.diagsCache[uri]
if !ok {
fileDiags = make(map[string][]lsp.Diagnostic)
}

fileDiags[source] = diags
n.diagsCache[uri] = fileDiags

all := []lsp.Diagnostic{}
for _, diags := range fileDiags {
all = append(all, diags...)
}
return all
}

func FromHCLMap(hclDiags map[string]hcl.Diagnostics) map[string][]lsp.Diagnostic {
diags := make(map[string][]lsp.Diagnostic, len(hclDiags))
for file, ds := range hclDiags {
diags[file] = ilsp.HCLDiagsToLSP(ds, "HCL")
}
return diags
}
10 changes: 5 additions & 5 deletions langserver/diagnostics/diagnostics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"log"
"testing"

"github.com/hashicorp/hcl/v2"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
)

var discardLogger = log.New(ioutil.Discard, "", 0)
Expand All @@ -16,10 +16,10 @@ func TestDiags_Closes(t *testing.T) {
n := NewNotifier(ctx, discardLogger)

cancel()
n.Publish(context.Background(), "", map[string]hcl.Diagnostics{
n.Publish(context.Background(), "", map[string][]lsp.Diagnostic{
"test": {
{
Severity: hcl.DiagError,
Severity: lsp.SeverityError,
},
},
}, "test")
Expand All @@ -40,10 +40,10 @@ func TestPublish_DoesNotSendAfterClose(t *testing.T) {
n := NewNotifier(ctx, discardLogger)

cancel()
n.Publish(context.Background(), "", map[string]hcl.Diagnostics{
n.Publish(context.Background(), "", map[string][]lsp.Diagnostic{
"test": {
{
Severity: hcl.DiagError,
Severity: lsp.SeverityError,
},
},
}, "test")
Expand Down
3 changes: 2 additions & 1 deletion langserver/handlers/did_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
lsctx "github.com/hashicorp/terraform-ls/internal/context"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
"github.com/hashicorp/terraform-ls/langserver/diagnostics"
)

func TextDocumentDidChange(ctx context.Context, params lsp.DidChangeTextDocumentParams) error {
Expand Down Expand Up @@ -67,7 +68,7 @@ func TextDocumentDidChange(ctx context.Context, params lsp.DidChangeTextDocument
if err != nil {
return err
}
diags.Publish(ctx, rm.Path(), rm.ParsedDiagnostics(), "HCL")
diags.Publish(ctx, rm.Path(), diagnostics.FromHCLMap(rm.ParsedDiagnostics()), "hcl")

return nil
}
3 changes: 2 additions & 1 deletion langserver/handlers/did_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
"github.com/hashicorp/terraform-ls/internal/terraform/rootmodule"
"github.com/hashicorp/terraform-ls/internal/watcher"
"github.com/hashicorp/terraform-ls/langserver/diagnostics"
)

func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpenTextDocumentParams) error {
Expand Down Expand Up @@ -73,7 +74,7 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
if err != nil {
return err
}
diags.Publish(ctx, rm.Path(), rm.ParsedDiagnostics(), "HCL")
diags.Publish(ctx, rm.Path(), diagnostics.FromHCLMap(rm.ParsedDiagnostics()), "hcl")

candidates := rmm.RootModuleCandidatesByPath(f.Dir())

Expand Down
Loading

0 comments on commit 5254615

Please sign in to comment.