From 4e4489c2a7d74e7f09fc3f2dc194d50ff6d3318b Mon Sep 17 00:00:00 2001 From: njucz Date: Fri, 13 Nov 2020 16:01:30 +0800 Subject: [PATCH 1/3] ask for init if current folder is not an initialized root module --- internal/terraform/exec/exec.go | 11 +++ internal/terraform/exec/mock/executor.go | 14 ++++ internal/terraform/exec/types.go | 1 + internal/terraform/rootmodule/root_module.go | 10 +++ .../rootmodule/root_module_manager.go | 15 ++++ internal/terraform/rootmodule/types.go | 2 + langserver/handlers/did_open.go | 68 ++++++++++++++++--- langserver/handlers/service.go | 1 + 8 files changed, 112 insertions(+), 10 deletions(-) diff --git a/internal/terraform/exec/exec.go b/internal/terraform/exec/exec.go index 564f2a854..4036c6956 100644 --- a/internal/terraform/exec/exec.go +++ b/internal/terraform/exec/exec.go @@ -87,6 +87,17 @@ func (e *Executor) setLogPath(method string) error { return e.tf.SetLogPath(logPath) } +func (e *Executor) Init(ctx context.Context) error { + ctx, cancel := e.withTimeout(ctx) + defer cancel() + err := e.setLogPath("Init") + if err != nil { + return err + } + + return e.contextfulError(ctx, "Init", e.tf.Init(ctx)) +} + func (e *Executor) Format(ctx context.Context, input []byte) ([]byte, error) { ctx, cancel := e.withTimeout(ctx) defer cancel() diff --git a/internal/terraform/exec/mock/executor.go b/internal/terraform/exec/mock/executor.go index 603ddfc7c..5a9b7e7be 100644 --- a/internal/terraform/exec/mock/executor.go +++ b/internal/terraform/exec/mock/executor.go @@ -58,6 +58,20 @@ func (_m *Executor) GetExecPath() string { return r0 } +// Init provides a mock function with given fields: ctx +func (_m *Executor) Init(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // ProviderSchemas provides a mock function with given fields: ctx func (_m *Executor) ProviderSchemas(ctx context.Context) (*tfjson.ProviderSchemas, error) { ret := _m.Called(ctx) diff --git a/internal/terraform/exec/types.go b/internal/terraform/exec/types.go index 7637a688e..061cc4eae 100644 --- a/internal/terraform/exec/types.go +++ b/internal/terraform/exec/types.go @@ -22,6 +22,7 @@ type TerraformExecutor interface { SetExecLogPath(path string) error SetTimeout(duration time.Duration) GetExecPath() string + Init(ctx context.Context) error Format(ctx context.Context, input []byte) ([]byte, error) Version(ctx context.Context) (*version.Version, error) ProviderSchemas(ctx context.Context) (*tfjson.ProviderSchemas, error) diff --git a/internal/terraform/rootmodule/root_module.go b/internal/terraform/rootmodule/root_module.go index 682efd2ef..62274ad86 100644 --- a/internal/terraform/rootmodule/root_module.go +++ b/internal/terraform/rootmodule/root_module.go @@ -302,6 +302,16 @@ func (rm *rootModule) discoverTerraformExecutor(ctx context.Context) error { return nil } +func (rm *rootModule) ExecuteTerraformInit(ctx context.Context) error { + if !rm.IsTerraformAvailable() { + if err := rm.discoverTerraformExecutor(ctx); err != nil { + return err + } + } + + return rm.tfExec.Init(ctx) +} + func (rm *rootModule) discoverTerraformVersion(ctx context.Context) error { if rm.tfExec == nil { return errors.New("no terraform executor - unable to read version") diff --git a/internal/terraform/rootmodule/root_module_manager.go b/internal/terraform/rootmodule/root_module_manager.go index 801aa75af..5302de3a4 100644 --- a/internal/terraform/rootmodule/root_module_manager.go +++ b/internal/terraform/rootmodule/root_module_manager.go @@ -98,6 +98,21 @@ func (rmm *rootModuleManager) SetLogger(logger *log.Logger) { rmm.logger = logger } +func (rmm *rootModuleManager) InitAndUpdateRootModule(ctx context.Context, dir string) (RootModule, error) { + rm, err := rmm.RootModuleByPath(dir) + if err != nil { + return nil, fmt.Errorf("failed to get root module: %+v", err) + } + + if err := rm.ExecuteTerraformInit(ctx); err != nil { + return nil, fmt.Errorf("failed to init root module: %+v", err) + } + + rootModule := rm.(*rootModule) + rootModule.discoverCaches(ctx, dir) + return rm, rootModule.UpdateProviderSchemaCache(ctx, rootModule.pluginLockFile) +} + func (rmm *rootModuleManager) AddAndStartLoadingRootModule(ctx context.Context, dir string) (RootModule, error) { dir = filepath.Clean(dir) diff --git a/internal/terraform/rootmodule/types.go b/internal/terraform/rootmodule/types.go index 72087eb80..56c826ca2 100644 --- a/internal/terraform/rootmodule/types.go +++ b/internal/terraform/rootmodule/types.go @@ -40,6 +40,7 @@ type RootModuleManager interface { SetTerraformExecLogPath(logPath string) SetTerraformExecTimeout(timeout time.Duration) + InitAndUpdateRootModule(ctx context.Context, dir string) (RootModule, error) AddAndStartLoadingRootModule(ctx context.Context, dir string) (RootModule, error) WorkerPoolSize() int WorkerQueueSize() int @@ -81,6 +82,7 @@ type RootModule interface { TerraformFormatter() (exec.Formatter, error) HasTerraformDiscoveryFinished() bool IsTerraformAvailable() bool + ExecuteTerraformInit(ctx context.Context) error Modules() []ModuleRecord } diff --git a/langserver/handlers/did_open.go b/langserver/handlers/did_open.go index 4768c4c27..c888f9e09 100644 --- a/langserver/handlers/did_open.go +++ b/langserver/handlers/did_open.go @@ -3,6 +3,7 @@ package handlers import ( "context" "fmt" + "log" "path/filepath" "strings" @@ -10,6 +11,7 @@ import ( lsctx "github.com/hashicorp/terraform-ls/internal/context" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" "github.com/hashicorp/terraform-ls/internal/terraform/rootmodule" + "github.com/hashicorp/terraform-ls/internal/watcher" lsp "github.com/sourcegraph/go-lsp" ) @@ -42,6 +44,11 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe return err } + w, err := lsctx.Watcher(ctx) + if err != nil { + return err + } + rootDir, _ := lsctx.RootDirectory(ctx) readableDir := humanReadablePath(rootDir, f.Dir()) @@ -78,16 +85,10 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe lh.logger.Printf("walker has not finished walking yet, data may be inaccurate for %s", f.FullPath()) } else if len(candidates) == 0 { // TODO: Only notify once per f.Dir() per session - msg := fmt.Sprintf("No root module found for %q."+ - " Functionality may be limited."+ - // Unfortunately we can't be any more specific wrt where - // because we don't gather "init-able folders" in any way - " You may need to run terraform init"+ - " and reload your editor.", readableDir) - return jrpc2.PushNotify(ctx, "window/showMessage", lsp.ShowMessageParams{ - Type: lsp.MTWarning, - Message: msg, - }) + // TODO: if there is any change for the provider/module, ask for init. The did change handler should also implement this + if !rm.ParsedDiagnostics().HasErrors() && len(f.Text()) != 0 { + go askInitForEmptyRootModule(ctx, lh.logger, w, rootDir, f.Dir()) + } } if len(candidates) > 1 { @@ -134,3 +135,50 @@ func humanReadablePath(rootDir, path string) string { return relDir } + +func askInitForEmptyRootModule(ctx context.Context, logger *log.Logger, w watcher.Watcher, rootDir, dir string) { + msg := fmt.Sprintf("No root module found for %q."+ + " Functionality may be limited."+ + // Unfortunately we can't be any more specific wrt where + // because we don't gather "init-able folders" in any way + " You may need to run terraform init.", humanReadablePath(rootDir, dir)) + title := "run `terraform init`" + resp, err := jrpc2.PushCall(ctx, "window/showMessageRequest", lsp.ShowMessageRequestParams{ + Type: lsp.MTWarning, + Message: msg, + Actions: []lsp.MessageActionItem{ + { + Title: title, + }, + }, + }) + if err != nil { + logger.Printf("%+v", err) + return + } + var action lsp.MessageActionItem + if err := resp.UnmarshalResult(&action); err != nil { + logger.Printf("unmarshal MessageActionItem: %+v", err) + return + } + if action.Title == title { + rmm, err := lsctx.RootModuleManager(ctx) + if err != nil { + logger.Printf("%+v", err) + return + } + + rm, err := rmm.InitAndUpdateRootModule(ctx, dir) + if err != nil { + logger.Printf("failed to init root module %+v", err) + return + } + + paths := rm.PathsToWatch() + logger.Printf("Adding %d paths of root module for watching (%s)", len(paths), dir) + err = w.AddPaths(paths) + if err != nil { + logger.Printf("failed to add watch for dir (%s): %+v", dir, err) + } + } +} diff --git a/langserver/handlers/service.go b/langserver/handlers/service.go index 6a2107714..4ca941471 100644 --- a/langserver/handlers/service.go +++ b/langserver/handlers/service.go @@ -192,6 +192,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { ctx = lsctx.WithRootDirectory(ctx, &rootDir) ctx = lsctx.WithRootModuleManager(ctx, svc.modMgr) ctx = lsctx.WithRootModuleWalker(ctx, svc.walker) + ctx = lsctx.WithWatcher(ctx, ww) return handle(ctx, req, lh.TextDocumentDidOpen) }, "textDocument/didClose": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { From 6b5bcd694c1e3720d19a9f358f6637e81bda6233 Mon Sep 17 00:00:00 2001 From: njucz Date: Fri, 13 Nov 2020 16:15:13 +0800 Subject: [PATCH 2/3] update --- langserver/handlers/did_open.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langserver/handlers/did_open.go b/langserver/handlers/did_open.go index c888f9e09..297d3b861 100644 --- a/langserver/handlers/did_open.go +++ b/langserver/handlers/did_open.go @@ -86,7 +86,7 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe } else if len(candidates) == 0 { // TODO: Only notify once per f.Dir() per session // TODO: if there is any change for the provider/module, ask for init. The did change handler should also implement this - if !rm.ParsedDiagnostics().HasErrors() && len(f.Text()) != 0 { + if len(f.Text()) != 0 { go askInitForEmptyRootModule(ctx, lh.logger, w, rootDir, f.Dir()) } } From bb5af818ec816a2870cd2f67e5853c8b6cd2c132 Mon Sep 17 00:00:00 2001 From: njucz Date: Fri, 20 Nov 2020 11:53:10 +0800 Subject: [PATCH 3/3] update --- langserver/handlers/did_open.go | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/langserver/handlers/did_open.go b/langserver/handlers/did_open.go index 297d3b861..4d227f44d 100644 --- a/langserver/handlers/did_open.go +++ b/langserver/handlers/did_open.go @@ -3,7 +3,6 @@ package handlers import ( "context" "fmt" - "log" "path/filepath" "strings" @@ -85,10 +84,15 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe lh.logger.Printf("walker has not finished walking yet, data may be inaccurate for %s", f.FullPath()) } else if len(candidates) == 0 { // TODO: Only notify once per f.Dir() per session - // TODO: if there is any change for the provider/module, ask for init. The did change handler should also implement this - if len(f.Text()) != 0 { - go askInitForEmptyRootModule(ctx, lh.logger, w, rootDir, f.Dir()) - } + go func() { + err := askInitForEmptyRootModule(ctx, w, rootDir, f.Dir()) + if err != nil { + jrpc2.PushNotify(ctx, "window/showMessage", lsp.ShowMessageParams{ + Type: lsp.MTError, + Message: err.Error(), + }) + } + }() } if len(candidates) > 1 { @@ -136,15 +140,15 @@ func humanReadablePath(rootDir, path string) string { return relDir } -func askInitForEmptyRootModule(ctx context.Context, logger *log.Logger, w watcher.Watcher, rootDir, dir string) { +func askInitForEmptyRootModule(ctx context.Context, w watcher.Watcher, rootDir, dir string) error { msg := fmt.Sprintf("No root module found for %q."+ " Functionality may be limited."+ // Unfortunately we can't be any more specific wrt where // because we don't gather "init-able folders" in any way " You may need to run terraform init.", humanReadablePath(rootDir, dir)) - title := "run `terraform init`" + title := "terraform init" resp, err := jrpc2.PushCall(ctx, "window/showMessageRequest", lsp.ShowMessageRequestParams{ - Type: lsp.MTWarning, + Type: lsp.Info, Message: msg, Actions: []lsp.MessageActionItem{ { @@ -153,32 +157,28 @@ func askInitForEmptyRootModule(ctx context.Context, logger *log.Logger, w watche }, }) if err != nil { - logger.Printf("%+v", err) - return + return err } var action lsp.MessageActionItem if err := resp.UnmarshalResult(&action); err != nil { - logger.Printf("unmarshal MessageActionItem: %+v", err) - return + return fmt.Errorf("unmarshal MessageActionItem: %+v", err) } if action.Title == title { rmm, err := lsctx.RootModuleManager(ctx) if err != nil { - logger.Printf("%+v", err) - return + return err } rm, err := rmm.InitAndUpdateRootModule(ctx, dir) if err != nil { - logger.Printf("failed to init root module %+v", err) - return + return fmt.Errorf("failed to init root module %+v", err) } paths := rm.PathsToWatch() - logger.Printf("Adding %d paths of root module for watching (%s)", len(paths), dir) err = w.AddPaths(paths) if err != nil { - logger.Printf("failed to add watch for dir (%s): %+v", dir, err) + return fmt.Errorf("failed to add watch for dir (%s): %+v", dir, err) } } + return nil }