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

internal: expose tf related options via init opts #588

Merged
merged 5 commits into from
Aug 12, 2021
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
7 changes: 7 additions & 0 deletions docs/SETTINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

The language server supports the following configuration options:

## `terraformExecPath` (`string`)

Path to the Terraform binary.

This is usually looked up automatically from `$PATH` and should not need to be
specified in majority of cases. Use this to override the automatic lookup.

## `rootModulePaths` (`[]string`)

This allows overriding automatic root module discovery by passing a static list
Expand Down
6 changes: 5 additions & 1 deletion internal/cmd/serve_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (c *ServeCommand) flags() *flag.FlagSet {
fs.IntVar(&c.port, "port", 0, "port number to listen on (turns server into TCP mode)")
fs.StringVar(&c.logFilePath, "log-file", "", "path to a file to log into with support "+
"for variables (e.g. Timestamp, Pid, Ppid) via Go template syntax {{.VarName}}")
fs.StringVar(&c.tfExecPath, "tf-exec", "", "path to Terraform binary")
fs.StringVar(&c.tfExecPath, "tf-exec", "", "(DEPRECATED) path to Terraform binary. Use terraformExecPath LSP config option instead.")
fs.StringVar(&c.tfExecTimeout, "tf-exec-timeout", "", "Overrides Terraform execution timeout (e.g. 30s)")
fs.StringVar(&c.tfExecLogPath, "tf-log-file", "", "path to a file for Terraform executions"+
" to be logged into with support for variables (e.g. Timestamp, Pid, Ppid) via Go template"+
Expand Down Expand Up @@ -120,6 +120,9 @@ func (c *ServeCommand) Run(args []string) int {
logger.Printf("Terraform execution timeout set to %s", d)
}

// Setting this option as a CLI flag is deprecated
// in favor of `terraformExecPath` LSP config option.
// This validation code is duplicated, make changes accordingly.
if c.tfExecPath != "" {
path := c.tfExecPath

Expand All @@ -140,6 +143,7 @@ func (c *ServeCommand) Run(args []string) int {

ctx = lsctx.WithTerraformExecPath(ctx, path)
logger.Printf("Terraform exec path set to %q", path)
logger.Println("[WARN] -tf-exec is deprecated in favor of `terraformExecPath` LSP config option")
}

if c.reqConcurrency != 0 {
Expand Down
2 changes: 2 additions & 0 deletions internal/langserver/handlers/formatting.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func (h *logHandler) TextDocumentFormatting(ctx context.Context, params lsp.Docu
return edits, err
}

h.logger.Printf("formatting document via %q", tfExec.GetExecPath())

formatted, err := tfExec.Format(ctx, original)
if err != nil {
return edits, err
Expand Down
30 changes: 12 additions & 18 deletions internal/langserver/handlers/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/mitchellh/go-homedir"
)

func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParams) (lsp.InitializeResult, error) {
func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams) (lsp.InitializeResult, error) {
serverCaps := lsp.InitializeResult{
Capabilities: lsp.ServerCapabilities{
TextDocumentSync: lsp.TextDocumentSyncOptions{
Expand Down Expand Up @@ -83,16 +83,16 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
return serverCaps, err
}

w, err := lsctx.Watcher(ctx)
out, err := settings.DecodeOptions(params.InitializationOptions)
if err != nil {
return serverCaps, err
}

out, err := settings.DecodeOptions(params.InitializationOptions)
err = out.Options.Validate()
if err != nil {
return serverCaps, err
}
err = out.Options.Validate()

err = svc.configureSessionDependencies(out.Options)
if err != nil {
return serverCaps, err
}
Expand Down Expand Up @@ -140,32 +140,26 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
})
}

walker, err := lsctx.ModuleWalker(ctx)
if err != nil {
return serverCaps, err
}
walker.SetLogger(lh.logger)

var excludeModulePaths []string
for _, rawPath := range cfgOpts.ExcludeModulePaths {
modPath, err := resolvePath(rootDir, rawPath)
if err != nil {
lh.logger.Printf("Ignoring excluded module path %s: %s", rawPath, err)
svc.logger.Printf("Ignoring excluded module path %s: %s", rawPath, err)
continue
}
excludeModulePaths = append(excludeModulePaths, modPath)
}

walker.SetExcludeModulePaths(excludeModulePaths)
walker.EnqueuePath(fh.Dir())
svc.walker.SetExcludeModulePaths(excludeModulePaths)
svc.walker.EnqueuePath(fh.Dir())

// Walker runs asynchronously so we're intentionally *not*
// passing the request context here
walkerCtx := context.Background()

// Walker is also started early to allow gradual consumption
// and avoid overfilling the queue
err = walker.StartWalking(walkerCtx)
err = svc.walker.StartWalking(walkerCtx)
if err != nil {
return serverCaps, err
}
Expand All @@ -182,13 +176,13 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
continue
}

walker.EnqueuePath(modPath)
svc.walker.EnqueuePath(modPath)
}
}

// Static user-provided paths take precedence over dynamic discovery
if len(cfgOpts.ModulePaths) > 0 {
lh.logger.Printf("Attempting to add %d static module paths", len(cfgOpts.ModulePaths))
svc.logger.Printf("Attempting to add %d static module paths", len(cfgOpts.ModulePaths))
for _, rawPath := range cfgOpts.ModulePaths {
modPath, err := resolvePath(rootDir, rawPath)
if err != nil {
Expand All @@ -199,7 +193,7 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
continue
}

err = w.AddModule(modPath)
err = svc.watcher.AddModule(modPath)
if err != nil {
return serverCaps, err
}
Expand Down
127 changes: 71 additions & 56 deletions internal/langserver/handlers/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type service struct {
newWalker module.WalkerFactory
tfDiscoFunc discovery.DiscoveryFunc
tfExecFactory exec.ExecutorFactory
tfExecOpts *exec.ExecutorOpts

additionalHandlers map[string]rpch.Func
}
Expand Down Expand Up @@ -92,53 +93,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
lh := LogHandler(svc.logger)
cc := &lsp.ClientCapabilities{}

// The following is set via CLI flags, hence available in the server context
execOpts := &exec.ExecutorOpts{}
if path, ok := lsctx.TerraformExecPath(svc.srvCtx); ok {
execOpts.ExecPath = path
} else {
tfExecPath, err := svc.tfDiscoFunc()
if err == nil {
execOpts.ExecPath = tfExecPath
}
}
if path, ok := lsctx.TerraformExecLogPath(svc.srvCtx); ok {
execOpts.ExecLogPath = path
}
if timeout, ok := lsctx.TerraformExecTimeout(svc.srvCtx); ok {
execOpts.Timeout = timeout
}

svc.sessCtx = exec.WithExecutorOpts(svc.sessCtx, execOpts)
svc.sessCtx = exec.WithExecutorFactory(svc.sessCtx, svc.tfExecFactory)

store, err := state.NewStateStore()
if err != nil {
return nil, err
}
store.SetLogger(svc.logger)

err = schemas.PreloadSchemasToStore(store.ProviderSchemas)
if err != nil {
return nil, err
}

svc.modMgr = svc.newModuleManager(svc.sessCtx, svc.fs, store.Modules, store.ProviderSchemas)
svc.modMgr.SetLogger(svc.logger)

svc.walker = svc.newWalker(svc.fs, svc.modMgr)

ww, err := svc.newWatcher(svc.fs, svc.modMgr)
if err != nil {
return nil, err
}
svc.watcher = ww
svc.watcher.SetLogger(svc.logger)
err = svc.watcher.Start()
if err != nil {
return nil, err
}

notifier := diagnostics.NewNotifier(svc.sessCtx, svc.logger)

rootDir := ""
Expand All @@ -152,10 +106,8 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
if err != nil {
return nil, err
}
ctx = lsctx.WithDocumentStorage(ctx, svc.fs)

ctx = lsctx.WithClientCapabilitiesSetter(ctx, cc)
ctx = lsctx.WithWatcher(ctx, ww)
ctx = lsctx.WithModuleWalker(ctx, svc.walker)
ctx = lsctx.WithRootDirectory(ctx, &rootDir)
ctx = lsctx.WithCommandPrefix(ctx, &commandPrefix)
ctx = lsctx.WithClientName(ctx, &clientName)
Expand All @@ -166,7 +118,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
ctx = lsctx.WithLanguageServerVersion(ctx, version)
}

return handle(ctx, req, lh.Initialize)
return handle(ctx, req, svc.Initialize)
},
"initialized": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
err := session.ConfirmInitialization(req)
Expand Down Expand Up @@ -194,7 +146,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = lsctx.WithDocumentStorage(ctx, svc.fs)
ctx = lsctx.WithModuleManager(ctx, svc.modMgr)
ctx = lsctx.WithWatcher(ctx, ww)
ctx = lsctx.WithWatcher(ctx, svc.watcher)
return handle(ctx, req, lh.TextDocumentDidOpen)
},
"textDocument/didClose": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
Expand Down Expand Up @@ -298,7 +250,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
}

ctx = lsctx.WithDocumentStorage(ctx, svc.fs)
ctx = exec.WithExecutorOpts(ctx, execOpts)
ctx = exec.WithExecutorOpts(ctx, svc.tfExecOpts)
ctx = exec.WithExecutorFactory(ctx, svc.tfExecFactory)

return handle(ctx, req, lh.TextDocumentFormatting)
Expand All @@ -324,7 +276,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = lsctx.WithExperimentalFeatures(ctx, &expFeatures)
ctx = lsctx.WithModuleFinder(ctx, svc.modMgr)
ctx = exec.WithExecutorOpts(ctx, execOpts)
ctx = exec.WithExecutorOpts(ctx, svc.tfExecOpts)

return handle(ctx, req, lh.TextDocumentDidSave)
},
Expand Down Expand Up @@ -360,10 +312,10 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
ctx = lsctx.WithModuleManager(ctx, svc.modMgr)
ctx = lsctx.WithModuleFinder(ctx, svc.modMgr)
ctx = lsctx.WithModuleWalker(ctx, svc.walker)
ctx = lsctx.WithWatcher(ctx, ww)
ctx = lsctx.WithWatcher(ctx, svc.watcher)
ctx = lsctx.WithRootDirectory(ctx, &rootDir)
ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = exec.WithExecutorOpts(ctx, execOpts)
ctx = exec.WithExecutorOpts(ctx, svc.tfExecOpts)
ctx = exec.WithExecutorFactory(ctx, svc.tfExecFactory)

return handle(ctx, req, lh.WorkspaceExecuteCommand)
Expand Down Expand Up @@ -419,6 +371,69 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
return convertMap(m), nil
}

func (svc *service) configureSessionDependencies(cfgOpts *settings.Options) error {
// The following is set via CLI flags, hence available in the server context
execOpts := &exec.ExecutorOpts{}
cliExecPath, ok := lsctx.TerraformExecPath(svc.srvCtx)
if ok {
if len(cfgOpts.TerraformExecPath) > 0 {
return fmt.Errorf("Terraform exec path can either be set via (-tf-exec) CLI flag " +
"or (terraformExecPath) LSP config option, not both")
}
execOpts.ExecPath = cliExecPath
} else if len(cfgOpts.TerraformExecPath) > 0 {
execOpts.ExecPath = cfgOpts.TerraformExecPath
} else {
path, err := svc.tfDiscoFunc()
if err == nil {
execOpts.ExecPath = path
}
}
svc.srvCtx = lsctx.WithTerraformExecPath(svc.srvCtx, execOpts.ExecPath)

if path, ok := lsctx.TerraformExecLogPath(svc.srvCtx); ok {
execOpts.ExecLogPath = path
}
if timeout, ok := lsctx.TerraformExecTimeout(svc.srvCtx); ok {
execOpts.Timeout = timeout
}

svc.tfExecOpts = execOpts

svc.sessCtx = exec.WithExecutorOpts(svc.sessCtx, execOpts)
svc.sessCtx = exec.WithExecutorFactory(svc.sessCtx, svc.tfExecFactory)

store, err := state.NewStateStore()
if err != nil {
return err
}
store.SetLogger(svc.logger)

err = schemas.PreloadSchemasToStore(store.ProviderSchemas)
if err != nil {
return err
}

svc.modMgr = svc.newModuleManager(svc.sessCtx, svc.fs, store.Modules, store.ProviderSchemas)
svc.modMgr.SetLogger(svc.logger)

svc.walker = svc.newWalker(svc.fs, svc.modMgr)
svc.walker.SetLogger(svc.logger)

ww, err := svc.newWatcher(svc.fs, svc.modMgr)
if err != nil {
return err
}
svc.watcher = ww
svc.watcher.SetLogger(svc.logger)
err = svc.watcher.Start()
if err != nil {
return err
}

return nil
}

func (svc *service) Finish(_ jrpc2.Assigner, status jrpc2.ServerStatus) {
if status.Closed || status.Err != nil {
svc.logger.Printf("session stopped unexpectedly (err: %v)", status.Err)
Expand Down
19 changes: 18 additions & 1 deletion internal/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package settings

import (
"fmt"
"os"
"path/filepath"

"github.com/mitchellh/mapstructure"
)
Expand All @@ -20,7 +22,7 @@ type Options struct {
ExperimentalFeatures ExperimentalFeatures `mapstructure:"experimentalFeatures"`

// TODO: Need to check for conflict with CLI flags
// TerraformExecPath string
TerraformExecPath string `mapstructure:"terraformExecPath"`
// TerraformExecTimeout time.Duration
// TerraformLogFilePath string
}
Expand All @@ -29,6 +31,21 @@ func (o *Options) Validate() error {
if len(o.ModulePaths) != 0 && len(o.ExcludeModulePaths) != 0 {
return fmt.Errorf("at most one of `rootModulePaths` and `excludeModulePaths` could be set")
}

if o.TerraformExecPath != "" {
path := o.TerraformExecPath
if !filepath.IsAbs(path) {
return fmt.Errorf("Expected absolute path for Terraform binary, got %q", path)
}
stat, err := os.Stat(path)
if err != nil {
return fmt.Errorf("Unable to find Terraform binary: %s", err)
}
if stat.IsDir() {
return fmt.Errorf("Expected a Terraform binary, got a directory: %q", path)
}
}

return nil
}

Expand Down
5 changes: 4 additions & 1 deletion internal/terraform/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,11 @@ func (e *Executor) Validate(ctx context.Context) ([]tfjson.Diagnostic, error) {
}

validation, err := e.tf.Validate(ctx)
if err != nil {
return []tfjson.Diagnostic{}, e.contextfulError(ctx, "Validate", err)
}

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

func (e *Executor) Version(ctx context.Context) (*version.Version, map[string]*version.Version, error) {
Expand Down