Skip to content

Commit

Permalink
Merge branch 'master' of github.51.al-juliosueiras:juliosueiras/terrafo…
Browse files Browse the repository at this point in the history
…rm-lsp
  • Loading branch information
juliosueiras committed Aug 28, 2019
2 parents cb72e8e + 4be8443 commit c28a857
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 132 deletions.
28 changes: 21 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
language: nix

matrix:
jobs:
include:
- os: linux
env: TRAVIS_OS=linux
- os: osx
env: TRAVIS_OS=osx
- script: echo "Testing"
# - language: nix
# os: linux
# env: TRAVIS_OS=linux
# - language: nix
# os: osx
# env: TRAVIS_OS=osx
- stage: GoReleaser Deployment
services:
- docker
language: go
#if: tag IS present
script: skip
deploy:
- provider: script
skip_cleanup: true
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true
condition: $TRAVIS_OS_NAME = linux

after_script:
- file ./result/bin/terraform-lsp
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,17 @@ This is LSP(Language Server Protocol) for Terraform

### Emacs

- Should work with emacs-lsp(need confirmation)
- Work with [emacs-lsp/lsp-mode](https://github.com/emacs-lsp/lsp-mode) while still a little buggy
```lisp
(add-to-list 'lsp-language-id-configuration '(terraform-mode . "terraform"))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection '("/path/to/terraform-lsp/terraform-lsp" "-enable-log-file"))
:major-modes '(terraform-mode)
:server-id 'terraform-ls))
(add-hook 'terraform-mode-hook #'lsp)
```

### Others

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/gogo/protobuf v1.2.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/go-hclog v0.9.0 // indirect
github.com/hashicorp/go-plugin v1.0.1-0.20190610192547-a1bc61569a26
github.com/hashicorp/go-version v1.2.0 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/hashicorp/hcl v1.0.0
Expand All @@ -24,7 +25,6 @@ require (
github.com/ulikunitz/xz v0.5.6 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f
github.com/zserge/webview v0.0.0-20190123072648-16c93bcaeaeb
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
golang.org/x/sys v0.0.0-20190508100423-12bbe5a7a520 // indirect
Expand Down
122 changes: 0 additions & 122 deletions go.sum

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,21 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com
// Block is Block, not resources
//test := config.BlocksAtPos(posHCL)
if blocks != nil && attr == nil {
if blocks[0].Type == "dynamic" {
//helper.DumpLog(blocks)
if blocks[0].Type == "provisioner" {
helper.DumpLog(blocks)
if len(blocks) == 1 {

if r, found, _ := tfstructs.GetAttributeCompletion(result, "provisioner", blocks[0], fileDir); found {
return r, nil
}
} else {
if r, found, _ := tfstructs.GetNestingCompletion(blocks[1:], result, "provisioner", blocks[0], fileDir); found {
return r, nil
}
}

} else if blocks[0].Type == "dynamic" {
if len(blocks) == 1 {
result = append(result, lsp.CompletionItem{
Label: "for_each",
Expand Down
42 changes: 42 additions & 0 deletions tfstructs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/lang"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/provisioners"
"github.com/juliosueiras/terraform-lsp/helper"
"github.com/zclconf/go-cty/cty"
"path/filepath"
Expand All @@ -18,6 +19,12 @@ type TerraformSchema struct {
Diags hcl.Diagnostics
}

type TerraformProvisionerSchema struct {
Schema *provisioners.GetSchemaResponse
DecodedSchema cty.Value
Diags hcl.Diagnostics
}

func GetModuleVariables(moduleAddr string, config hcl.Body, targetDir string) (map[string]*configs.Variable, bool) {
parser := configs.NewParser(nil)

Expand Down Expand Up @@ -131,6 +138,41 @@ func GetProvider(providerType string, targetDir string) (*Client, error) {
return provider, err
}

func GetProvisioner(provisionerType string, targetDir string) (*Client, error) {
provisioner, err := NewProvisionerClient(provisionerType, targetDir)
return provisioner, err
}

func GetProvisionerSchema(provisionerType string, config hcl.Body, targetDir string) *TerraformProvisionerSchema {
provisioner, err := GetProvider(provisionerType, targetDir)
if err != nil {
helper.DumpLog(err)
return nil
}

provisionerSchema := provisioner.provisioner.GetSchema()

provisioner.Kill()

res2 := provisionerSchema.Provisioner.DecoderSpec()
scope := lang.Scope{}

res, _, diags := hcldec.PartialDecode(config, res2, &hcl.EvalContext{
Variables: map[string]cty.Value{
"data": cty.DynamicVal,
"var": cty.DynamicVal, // Need to check for undefined vars
"module": cty.DynamicVal,
},
Functions: scope.Functions(),
})

return &TerraformProvisionerSchema{
Schema: &provisionerSchema,
DecodedSchema: res,
Diags: diags,
}
}

func GetProviderSchema(providerType string, config hcl.Body, targetDir string) *TerraformSchema {
provider, err := GetProvider(providerType, targetDir)
if err != nil {
Expand Down
79 changes: 79 additions & 0 deletions tfstructs/parser_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func GetNestingAttributeCompletion(attr *hcl.Attribute, result []lsp.CompletionI
Items: helper.ParseVariables(resultTraversal, moduleVars, result),
}, true, nil
}
case "provisioner":
case "resource":
case "data":
return lsp.CompletionList{
Expand Down Expand Up @@ -248,7 +249,40 @@ func GetAttributeCompletion(result []lsp.CompletionItem, configType string, orig
IsIncomplete: false,
Items: result,
}, true, nil
case "provisioner":
origConfig := origConfig.(*hcl.Block)

res, _ := GetProvisioner(origConfig.Labels[0], fileDir)
if res == nil {
return lsp.CompletionList{
IsIncomplete: false,
Items: result,
}, true, nil
}

defer res.Kill()
schema, _ := res.GetRawProvisionerSchema()

for k, v := range schema.Provisioner.Attributes {
if v.Optional || v.Required {
result = append(result, lsp.CompletionItem{
Label: k,
Detail: fmt.Sprintf(" (%s) %s", checkRequire(v), v.Type.FriendlyName()),
Documentation: v.Description,
})
}
}

for p, v := range schema.Provisioner.BlockTypes {
result = append(result, lsp.CompletionItem{
Label: p,
Detail: " " + v.Nesting.String(),
})
}
return lsp.CompletionList{
IsIncomplete: false,
Items: result,
}, true, nil
case "resource":
origConfig := origConfig.(*configs.Resource)
var providerType string
Expand Down Expand Up @@ -380,6 +414,51 @@ func GetNestingCompletion(blocks []*hcl.Block, result []lsp.CompletionItem, conf
}
}

if resultBlock != nil {
for k, v := range resultBlock.Attributes {
if v.Optional || v.Required {
result = append(result, lsp.CompletionItem{
Label: k,
Detail: fmt.Sprintf(" (%s) %s", checkRequire(v), v.Type.FriendlyName()),
Documentation: v.Description,
})
}
}

for p, v := range resultBlock.BlockTypes {
result = append(result, lsp.CompletionItem{
Label: p,
Detail: " " + v.Nesting.String(),
})
}
return lsp.CompletionList{
IsIncomplete: false,
Items: result,
}, true, nil
}
case "provisioner":
origConfig := origConfig.(*hcl.Block)

res, _ := GetProvisioner(origConfig.Labels[0], fileDir)
if res == nil {
return lsp.CompletionList{
IsIncomplete: false,
Items: result,
}, true, nil
}

defer res.Kill()
schema, _ := res.GetRawProvisionerSchema()

var resultBlock *configschema.NestedBlock
searchBlockTypes := schema.Provisioner.BlockTypes
for _, block := range blocks {
if searchBlockTypes[block.Type] != nil {
resultBlock = searchBlockTypes[block.Type]
searchBlockTypes = searchBlockTypes[block.Type].BlockTypes
}
}

if resultBlock != nil {
for k, v := range resultBlock.Attributes {
if v.Optional || v.Required {
Expand Down
78 changes: 78 additions & 0 deletions tfstructs/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,96 @@ package tfstructs

import (
"fmt"
internalPlugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/provisioners"
"github.com/mitchellh/go-homedir"
"go/build"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strings"
)

var internalProvisionerList = map[string]bool{
"chef": true,
"file": true,
"habitat": true,
"local-exec": true,
"puppet": true,
"remote-exec": true,
"salt-masterless": true,
}

// Client represents a tfschema Client.
type Client struct {
provider *plugin.GRPCProvider
provisioner *plugin.GRPCProvisioner
pluginClient interface{}
}

// NewProvisionerClient creates a new Client instance for Provisioner.
func NewProvisionerClient(name string, targetDir string) (*Client, error) {
// find a provisioner plugin
if !internalProvisionerList[name] {

pluginMeta, err := findPlugin("provisioner", name, targetDir)
if err != nil {
return nil, err
}
pluginClient := plugin.Client(*pluginMeta)
rpcClient, err2 := pluginClient.Client()
if err2 != nil {
return nil, fmt.Errorf("Failed to initialize plugin: %s", err2)
}
// create a new resource provisioner.
raw, err := rpcClient.Dispense(plugin.ProvisionerPluginName)
if err != nil {
return nil, fmt.Errorf("Failed to dispense plugin: %s", err)
}

provisioner := raw.(*plugin.GRPCProvisioner)

return &Client{
provisioner: provisioner,
pluginClient: pluginClient,
}, nil

} else {
// initialize a plugin Client.
pluginClientConfig := plugin.ClientConfig(discovery.PluginMeta{
Name: "terraform",
})

res, _ := exec.LookPath("terraform")
pluginClientConfig.Cmd = exec.Command(res, "internal-plugin", "provisioner", name)

pluginClient := internalPlugin.NewClient(pluginClientConfig)
var err error
rpcClient, err := pluginClient.Client()
if err != nil {
return nil, fmt.Errorf("Failed to initialize plugin: %s", err)
}
// create a new resource provisioner.
raw, err := rpcClient.Dispense(plugin.ProvisionerPluginName)
if err != nil {
return nil, fmt.Errorf("Failed to dispense plugin: %s", err)
}

provisioner := raw.(*plugin.GRPCProvisioner)

return &Client{
provisioner: provisioner,
pluginClient: pluginClient,
}, nil
}
}

// NewClient creates a new Client instance.
func NewClient(providerName string, targetDir string) (*Client, error) {
// find a provider plugin
Expand Down Expand Up @@ -120,6 +191,13 @@ func (c *Client) GetRawProviderSchema() (*providers.Schema, error) {
return &res.Provider, nil
}

// GetRawProvisionerSchema returns a raw type definiton of provisioner schema.
func (c *Client) GetRawProvisionerSchema() (*provisioners.GetSchemaResponse, error) {

res := c.provisioner.GetSchema()
return &res, nil
}

// GetRawResourceTypeSchema returns a type definiton of resource type.
func (c *Client) GetRawResourceTypeSchema(resourceType string) (*providers.Schema, error) {

Expand Down

0 comments on commit c28a857

Please sign in to comment.