Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Terraform Validate Command #323

Merged
merged 11 commits into from
Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ 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/google/uuid v1.1.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-json v0.6.0
github.com/hashicorp/terraform-schema v0.0.0-20201204171308-0c9744a02c65
github.com/hashicorp/terraform-exec v0.11.1-0.20201207223938-9186a7c3bb24
github.com/hashicorp/terraform-json v0.7.0
github.com/hashicorp/terraform-schema v0.0.0-20201208163444-44d0347ab290
github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5
github.com/mitchellh/cli v1.1.1
github.com/mitchellh/go-homedir v1.1.0
Expand All @@ -24,6 +24,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
)
22 changes: 14 additions & 8 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 @@ -196,14 +198,14 @@ 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-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=
github.com/hashicorp/terraform-json v0.6.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU=
github.com/hashicorp/terraform-schema v0.0.0-20201204171308-0c9744a02c65 h1:CFZI/uaZCoAN0SI4A9edORfj+tnq+Hg0v9PH2AZ+1tA=
github.com/hashicorp/terraform-schema v0.0.0-20201204171308-0c9744a02c65/go.mod h1:tU5zEQwdtUJbMQXh5+s0TnBOFshXSAcdAx8Ki8p2S4w=
github.com/hashicorp/terraform-exec v0.11.1-0.20201207223938-9186a7c3bb24 h1:kMl+qKoUCH6Uj8wzlT+ovHMbJAFJgY1Zw6ek1yHwWyo=
github.com/hashicorp/terraform-exec v0.11.1-0.20201207223938-9186a7c3bb24/go.mod h1:o1PZgrOpDMiDeKT0Hcr2oo6KoVKyaeTN867jcaXMg4E=
github.com/hashicorp/terraform-json v0.7.0 h1:DgkfLARKMQ/xmzVtSRX9Vz/fzPCL3vskHIgj6s+SQwQ=
github.com/hashicorp/terraform-json v0.7.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE=
github.com/hashicorp/terraform-schema v0.0.0-20201208004742-b5e321a36f41 h1:FvVpjaQJXT9AH70Vs9i90QDTLz92BsL69N9kaLGK8qE=
github.com/hashicorp/terraform-schema v0.0.0-20201208004742-b5e321a36f41/go.mod h1:eRHMO4QL4TTka07aC7fH+AXvi/tYlv6udrA8nSFOl6g=
github.com/hashicorp/terraform-schema v0.0.0-20201208163444-44d0347ab290 h1:kAs5ZG+cgtWy3+81Z7G/Blj2imiDLfFBRaqmNs8mD4o=
github.com/hashicorp/terraform-schema v0.0.0-20201208163444-44d0347ab290/go.mod h1:eRHMO4QL4TTka07aC7fH+AXvi/tYlv6udrA8nSFOl6g=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
Expand Down Expand Up @@ -351,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 @@ -551,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
65 changes: 49 additions & 16 deletions internal/langserver/diagnostics/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,39 @@ import (
type diagContext struct {
ctx context.Context
uri lsp.DocumentURI
diags hcl.Diagnostics
source string
diags []lsp.Diagnostic
}

type diagnosticSource string

type fileDiagnostics map[diagnosticSource][]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[lsp.DocumentURI]fileDiagnostics
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[lsp.DocumentURI]fileDiagnostics),
}
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) {
// PublishHCLDiags accepts a map of hcl diagnostics per file and queues them for publishing.
// A dir path is passed which is joined with the filename keys of the map, to form a file URI.
// A source string is passed and set for each diagnostic, this is typically displayed in the client UI.
func (n *Notifier) PublishHCLDiags(ctx context.Context, dirPath string, diags map[string]hcl.Diagnostics, source string) {
select {
case <-n.sessCtx.Done():
n.closeDiagsOnce.Do(func() {
Expand All @@ -45,22 +58,42 @@ 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: ilsp.HCLDiagsToLSP(ds, source),
uri: lsp.DocumentURI(uri.FromPath(filepath.Join(dirPath, 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(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 lsp.DocumentURI, source string, diags []lsp.Diagnostic) []lsp.Diagnostic {

fileDiags, ok := n.diagsCache[uri]
if !ok {
fileDiags = make(fileDiagnostics)
}

fileDiags[diagnosticSource(source)] = diags
n.diagsCache[uri] = fileDiags

all := []lsp.Diagnostic{}
for _, diags := range fileDiags {
all = append(all, diags...)
}
return all
}
75 changes: 73 additions & 2 deletions internal/langserver/diagnostics/diagnostics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

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

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

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

cancel()
n.Publish(context.Background(), "", map[string]hcl.Diagnostics{
n.PublishHCLDiags(context.Background(), "", map[string]hcl.Diagnostics{
"test": {
{
Severity: hcl.DiagError,
},
},
}, "test")
}

func TestMergeDiags_CachesMultipleSourcesPerURI(t *testing.T) {
uri := lsp.DocumentURI("test.tf")

n := NewNotifier(context.Background(), discardLogger)

all := n.mergeDiags(uri, "source1", []lsp.Diagnostic{
{
Severity: lsp.SeverityError,
Message: "diag1",
},
})
if len(all) != 1 {
t.Fatalf("returns diags is incorrect length: expected %d, got %d", 1, len(all))
}

all = n.mergeDiags(uri, "source2", []lsp.Diagnostic{
{
Severity: lsp.SeverityError,
Message: "diag2",
},
})
if len(all) != 2 {
t.Fatalf("returns diags is incorrect length: expected %d, got %d", 2, len(all))
}
}

func TestMergeDiags_OverwritesSource_EvenWithEmptySlice(t *testing.T) {
uri := lsp.DocumentURI("test.tf")

n := NewNotifier(context.Background(), discardLogger)

all := n.mergeDiags(uri, "source1", []lsp.Diagnostic{
{
Severity: lsp.SeverityError,
Message: "diag1",
},
})
if len(all) != 1 {
t.Fatalf("returns diags is incorrect length: expected %d, got %d", 1, len(all))
}

all = n.mergeDiags(uri, "source1", []lsp.Diagnostic{
{
Severity: lsp.SeverityError,
Message: "diagOverwritten",
},
})
if len(all) != 1 {
t.Fatalf("returns diags is incorrect length: expected %d, got %d", 1, len(all))
}
if all[0].Message != "diagOverwritten" {
t.Fatalf("diag has incorrect message: expected %s, got %s", "diagOverwritten", all[0].Message)
}

all = n.mergeDiags(uri, "source2", []lsp.Diagnostic{
{
Severity: lsp.SeverityError,
Message: "diag2",
},
})
if len(all) != 2 {
t.Fatalf("returns diags is incorrect length: expected %d, got %d", 2, len(all))
}

all = n.mergeDiags(uri, "source2", []lsp.Diagnostic{})
if len(all) != 1 {
t.Fatalf("returns diags is incorrect length: expected %d, got %d", 1, len(all))
}
}
52 changes: 52 additions & 0 deletions internal/langserver/handlers/command/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package command

import (
"context"
"fmt"

"github.com/creachadair/jrpc2/code"
lsctx "github.com/hashicorp/terraform-ls/internal/context"
"github.com/hashicorp/terraform-ls/internal/langserver/cmd"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
)

func TerraformValidateHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) {
dirUri, ok := args.GetString("uri")
if !ok || dirUri == "" {
return nil, fmt.Errorf("%w: expected dir uri argument to be set", code.InvalidParams.Err())
}

dh := ilsp.FileHandlerFromDirURI(lsp.DocumentURI(dirUri))

cf, err := lsctx.RootModuleFinder(ctx)
if err != nil {
return nil, err
}

rm, err := cf.RootModuleByPath(dh.Dir())
if err != nil {
return nil, err
}

wasInit, err := rm.WasInitialized()
if err != nil {
return nil, fmt.Errorf("error checking if %s was initialized: %s", dirUri, err)
}
if !wasInit {
return nil, fmt.Errorf("%s is not an initialized module, terraform validate cannot be called", dirUri)
}

diags, err := lsctx.Diagnostics(ctx)
if err != nil {
return nil, err
}

hclDiags, err := rm.ExecuteTerraformValidate(ctx)
if err != nil {
return nil, err
}
diags.PublishHCLDiags(ctx, rm.Path(), hclDiags, "terraform validate")

return nil, nil
}
2 changes: 1 addition & 1 deletion internal/langserver/handlers/did_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TextDocumentDidChange(ctx context.Context, params lsp.DidChangeTextDocument
if err != nil {
return err
}
diags.Publish(ctx, rm.Path(), rm.ParsedDiagnostics(), "HCL")
diags.PublishHCLDiags(ctx, rm.Path(), rm.ParsedDiagnostics(), "HCL")

return nil
}
2 changes: 1 addition & 1 deletion internal/langserver/handlers/did_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,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.PublishHCLDiags(ctx, rm.Path(), rm.ParsedDiagnostics(), "HCL")

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

Expand Down
5 changes: 3 additions & 2 deletions internal/langserver/handlers/execute_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
)

var handlers = cmd.Handlers{
cmd.Name("rootmodules"): command.RootModulesHandler,
cmd.Name("terraform.init"): command.TerraformInitHandler,
cmd.Name("rootmodules"): command.RootModulesHandler,
cmd.Name("terraform.init"): command.TerraformInitHandler,
cmd.Name("terraform.validate"): command.TerraformValidateHandler,
}

func (lh *logHandler) WorkspaceExecuteCommand(ctx context.Context, params lsp.ExecuteCommandParams) (interface{}, error) {
Expand Down
Loading