Skip to content

Commit

Permalink
refactor: asynchronous loading of root module parts (#219)
Browse files Browse the repository at this point in the history
* rootmodules: Surface whole RootModule candidates instead of paths

* refactor: asynchronous loading of root module parts

* make test data accessible for linux & windows too

* address PR feedback
  • Loading branch information
radeksimko authored Jul 9, 2020
1 parent bf57c3e commit c41d592
Show file tree
Hide file tree
Showing 68 changed files with 995 additions and 418 deletions.
18 changes: 15 additions & 3 deletions commands/completion_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"

Expand Down Expand Up @@ -47,6 +48,13 @@ func (c *CompletionCommand) Run(args []string) int {
}

path := f.Arg(0)

path, err := filepath.Abs(path)
if err != nil {
c.Ui.Output(err.Error())
return 1
}

content, err := ioutil.ReadFile(path)
if err != nil {
c.Ui.Error(fmt.Sprintf("reading file at %q failed: %s", path, err))
Expand Down Expand Up @@ -102,16 +110,20 @@ func (c *CompletionCommand) Run(args []string) int {

w, err := rootmodule.NewRootModule(context.Background(), fh.Dir())
if err != nil {
c.Ui.Error(err.Error())
c.Ui.Error(fmt.Sprintf("failed to load root module: %s", err.Error()))
return 1
}
p, err := w.Parser()
if err != nil {
c.Ui.Error(fmt.Sprintf("failed to find parser: %s", err.Error()))
return 1
}
p := w.Parser()

pos := fPos.Position()

candidates, err := p.CompletionCandidatesAtPos(hclFile, pos)
if err != nil {
c.Ui.Error(err.Error())
c.Ui.Error(fmt.Sprintf("failed to find candidates: %s", err.Error()))
return 1
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/creachadair/jrpc2 v0.8.1
github.com/fsnotify/fsnotify v1.4.9
github.com/google/go-cmp v0.4.0
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/hcl/v2 v2.5.2-0.20200528183353-fa7c453538de
github.com/hashicorp/terraform-json v0.5.0
Expand Down
31 changes: 14 additions & 17 deletions internal/terraform/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ type cmdCtxFunc func(context.Context, string, ...string) *exec.Cmd

// ExecutorFactory can be used in external consumers of exec pkg
// to enable easy swapping with MockExecutor
type ExecutorFactory func(ctx context.Context, path string) *Executor
type ExecutorFactory func(path string) *Executor

type Executor struct {
ctx context.Context
timeout time.Duration

execPath string
Expand All @@ -55,9 +54,8 @@ type command struct {
StderrBuffer *bytes.Buffer
}

func NewExecutor(ctx context.Context, path string) *Executor {
func NewExecutor(path string) *Executor {
return &Executor{
ctx: ctx,
timeout: defaultExecTimeout,
execPath: path,
logger: log.New(ioutil.Discard, "", 0),
Expand Down Expand Up @@ -87,15 +85,14 @@ func (e *Executor) GetExecPath() string {
return e.execPath
}

func (e *Executor) cmd(args ...string) (*command, error) {
func (e *Executor) cmd(ctx context.Context, args ...string) (*command, error) {
if e.workDir == "" {
return nil, fmt.Errorf("no work directory set")
}

ctx := e.ctx
cancel := func() {}
if e.timeout > 0 {
ctx, cancel = context.WithTimeout(e.ctx, e.timeout)
ctx, cancel = context.WithTimeout(ctx, e.timeout)
}

var outBuf bytes.Buffer
Expand Down Expand Up @@ -186,8 +183,8 @@ func (e *Executor) runCmd(command *command) ([]byte, error) {
return e.waitCmd(command)
}

func (e *Executor) run(args ...string) ([]byte, error) {
cmd, err := e.cmd(args...)
func (e *Executor) run(ctx context.Context, args ...string) ([]byte, error) {
cmd, err := e.cmd(ctx, args...)
e.logger.Printf("running with timeout %s", e.timeout)
defer cmd.CancelFunc()
if err != nil {
Expand All @@ -196,8 +193,8 @@ func (e *Executor) run(args ...string) ([]byte, error) {
return e.runCmd(cmd)
}

func (e *Executor) Format(input []byte) ([]byte, error) {
cmd, err := e.cmd("fmt", "-")
func (e *Executor) Format(ctx context.Context, input []byte) ([]byte, error) {
cmd, err := e.cmd(ctx, "fmt", "-")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -236,8 +233,8 @@ func writeAndClose(w io.WriteCloser, input []byte) (int, error) {
return n, nil
}

func (e *Executor) Version() (string, error) {
out, err := e.run("version")
func (e *Executor) Version(ctx context.Context) (string, error) {
out, err := e.run(ctx, "version")
if err != nil {
return "", fmt.Errorf("failed to get version: %w", err)
}
Expand All @@ -251,8 +248,8 @@ func (e *Executor) Version() (string, error) {
return version, nil
}

func (e *Executor) VersionIsSupported(c version.Constraints) error {
v, err := e.Version()
func (e *Executor) VersionIsSupported(ctx context.Context, c version.Constraints) error {
v, err := e.Version(ctx)
if err != nil {
return err
}
Expand All @@ -269,8 +266,8 @@ func (e *Executor) VersionIsSupported(c version.Constraints) error {
return nil
}

func (e *Executor) ProviderSchemas() (*tfjson.ProviderSchemas, error) {
outBytes, err := e.run("providers", "schema", "-json")
func (e *Executor) ProviderSchemas(ctx context.Context) (*tfjson.ProviderSchemas, error) {
outBytes, err := e.run(ctx, "providers", "schema", "-json")
if err != nil {
return nil, fmt.Errorf("failed to get schemas: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/terraform/exec/exec_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ func (mc *MockQueue) NextMockItem() *MockItem {
}

func MockExecutor(md MockItemDispenser) ExecutorFactory {
return func(ctx context.Context, path string) *Executor {
return func(path string) *Executor {
if md == nil {
md = &MockCall{
MockError: "no mocks provided",
}
}

path, ctxFunc := mockCommandCtxFunc(md)
executor := NewExecutor(context.Background(), path)
executor := NewExecutor(path)
executor.cmdCtxFunc = ctxFunc
return executor
}
Expand Down
16 changes: 8 additions & 8 deletions internal/terraform/exec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ func TestExec_timeout(t *testing.T) {
Args: []string{"version"},
SleepDuration: 100 * time.Millisecond,
Stdout: "Terraform v0.12.0\n",
})(context.Background(), "")
})("")
e.SetWorkdir(os.TempDir())
e.timeout = 1 * time.Millisecond

expectedErr := ExecTimeoutError([]string{"terraform", "version"}, e.timeout)

_, err := e.Version()
_, err := e.Version(context.Background())
if err != nil {
if errors.Is(err, expectedErr) {
return
Expand All @@ -38,9 +38,9 @@ func TestExec_Version(t *testing.T) {
Args: []string{"version"},
Stdout: "Terraform v0.12.0\n",
ExitCode: 0,
})(context.Background(), "")
})("")
e.SetWorkdir(os.TempDir())
v, err := e.Version()
v, err := e.Version(context.Background())
if err != nil {
t.Fatal(err)
}
Expand All @@ -55,9 +55,9 @@ func TestExec_Format(t *testing.T) {
Args: []string{"fmt", "-"},
Stdout: string(expectedOutput),
ExitCode: 0,
})(context.Background(), "")
})("")
e.SetWorkdir(os.TempDir())
out, err := e.Format([]byte("unformatted"))
out, err := e.Format(context.Background(), []byte("unformatted"))
if err != nil {
t.Fatal(err)
}
Expand All @@ -73,10 +73,10 @@ func TestExec_ProviderSchemas(t *testing.T) {
Args: []string{"providers", "schema", "-json"},
Stdout: `{"format_version": "0.1"}`,
ExitCode: 0,
})(context.Background(), "")
})("")
e.SetWorkdir(os.TempDir())

ps, err := e.ProviderSchemas()
ps, err := e.ProviderSchemas(context.Background())
if err != nil {
t.Fatal(err)
}
Expand Down
43 changes: 43 additions & 0 deletions internal/terraform/rootmodule/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package rootmodule

import (
"fmt"
"os"
)

func findFile(paths []string) (File, error) {
var lf File
var err error

for _, path := range paths {
lf, err = newFile(path)
if err == nil {
return lf, nil
}
if !os.IsNotExist(err) {
return nil, err
}
}

return nil, err
}

type file struct {
path string
}

func (f *file) Path() string {
return f.path
}

func newFile(path string) (File, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
if fi.IsDir() {
return nil, fmt.Errorf("expected %s to be a file, not a dir", path)
}

return &file{path: path}, nil
}
Loading

0 comments on commit c41d592

Please sign in to comment.