From 754eafb4f09b8c052d254a4e05c30849152e0842 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Tue, 26 Jul 2022 11:56:13 -0400 Subject: [PATCH 1/9] Add terraform version command This commit adds a new `module.terraform` command which returns the required and discovered version of Terraform in the currrent workspace. --- .../langserver/handlers/command/terraform.go | 56 +++++++++++++++++++ .../langserver/handlers/execute_command.go | 1 + 2 files changed, 57 insertions(+) create mode 100644 internal/langserver/handlers/command/terraform.go diff --git a/internal/langserver/handlers/command/terraform.go b/internal/langserver/handlers/command/terraform.go new file mode 100644 index 000000000..bfb4b3cab --- /dev/null +++ b/internal/langserver/handlers/command/terraform.go @@ -0,0 +1,56 @@ +package command + +import ( + "context" + "fmt" + + "github.com/creachadair/jrpc2/code" + "github.com/hashicorp/terraform-ls/internal/langserver/cmd" + "github.com/hashicorp/terraform-ls/internal/uri" +) + +const terraformVersionRequestVersion = 0 + +type terraformInfoResponse struct { + FormatVersion int `json:"v"` + RequiredVersion string `json:"required_version,omitempty"` + DiscoveredVersion string `json:"discovered_version,omitempty"` + DiscoveredPath string `json:"discovered_path,omitempty"` +} + +func (h *CmdHandler) TerraformVersionRequestHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) { + response := terraformInfoResponse{ + FormatVersion: terraformVersionRequestVersion, + } + + modUri, ok := args.GetString("uri") + if !ok || modUri == "" { + return response, fmt.Errorf("%w: expected module uri argument to be set", code.InvalidParams.Err()) + } + + if !uri.IsURIValid(modUri) { + return response, fmt.Errorf("URI %q is not valid", modUri) + } + + modPath, err := uri.PathFromURI(modUri) + if err != nil { + return response, err + } + + mod, _ := h.StateStore.Modules.ModuleByPath(modPath) + if mod == nil { + return response, nil + } + + if mod.TerraformVersion == nil { + return response, nil + } + if mod.Meta.CoreRequirements == nil { + return response, nil + } + + response.DiscoveredVersion = mod.TerraformVersion.String() + response.RequiredVersion = mod.Meta.CoreRequirements.String() + + return response, nil +} diff --git a/internal/langserver/handlers/execute_command.go b/internal/langserver/handlers/execute_command.go index 8164f4d96..a968246de 100644 --- a/internal/langserver/handlers/execute_command.go +++ b/internal/langserver/handlers/execute_command.go @@ -24,6 +24,7 @@ func cmdHandlers(svc *service) cmd.Handlers { cmd.Name("terraform.validate"): cmdHandler.TerraformValidateHandler, cmd.Name("module.calls"): cmdHandler.ModuleCallsHandler, cmd.Name("module.providers"): cmdHandler.ModuleProvidersHandler, + cmd.Name("module.terraform"): cmdHandler.TerraformVersionRequestHandler, } } From b15f21edfed4bc9659b48b3f0d3f2453b39c213d Mon Sep 17 00:00:00 2001 From: James Pogran Date: Fri, 29 Jul 2022 11:11:18 -0400 Subject: [PATCH 2/9] Add progress notification --- internal/langserver/handlers/command/terraform.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/langserver/handlers/command/terraform.go b/internal/langserver/handlers/command/terraform.go index bfb4b3cab..245123241 100644 --- a/internal/langserver/handlers/command/terraform.go +++ b/internal/langserver/handlers/command/terraform.go @@ -6,6 +6,7 @@ import ( "github.com/creachadair/jrpc2/code" "github.com/hashicorp/terraform-ls/internal/langserver/cmd" + "github.com/hashicorp/terraform-ls/internal/langserver/progress" "github.com/hashicorp/terraform-ls/internal/uri" ) @@ -19,10 +20,16 @@ type terraformInfoResponse struct { } func (h *CmdHandler) TerraformVersionRequestHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) { + progress.Begin(ctx, "Initializing") + defer func() { + progress.End(ctx, "Finished") + }() + response := terraformInfoResponse{ FormatVersion: terraformVersionRequestVersion, } + progress.Report(ctx, "Finding current module info ...") modUri, ok := args.GetString("uri") if !ok || modUri == "" { return response, fmt.Errorf("%w: expected module uri argument to be set", code.InvalidParams.Err()) @@ -42,6 +49,7 @@ func (h *CmdHandler) TerraformVersionRequestHandler(ctx context.Context, args cm return response, nil } + progress.Report(ctx, "Module info found ...") if mod.TerraformVersion == nil { return response, nil } @@ -49,6 +57,7 @@ func (h *CmdHandler) TerraformVersionRequestHandler(ctx context.Context, args cm return response, nil } + progress.Report(ctx, "Recording terraform version info ...") response.DiscoveredVersion = mod.TerraformVersion.String() response.RequiredVersion = mod.Meta.CoreRequirements.String() From 425f3435178260186fcc2f54f681fb5a57c35d14 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Fri, 29 Jul 2022 11:11:42 -0400 Subject: [PATCH 3/9] Remove discovered path until we can actually discover it --- internal/langserver/handlers/command/terraform.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/langserver/handlers/command/terraform.go b/internal/langserver/handlers/command/terraform.go index 245123241..277cea1c2 100644 --- a/internal/langserver/handlers/command/terraform.go +++ b/internal/langserver/handlers/command/terraform.go @@ -16,7 +16,6 @@ type terraformInfoResponse struct { FormatVersion int `json:"v"` RequiredVersion string `json:"required_version,omitempty"` DiscoveredVersion string `json:"discovered_version,omitempty"` - DiscoveredPath string `json:"discovered_path,omitempty"` } func (h *CmdHandler) TerraformVersionRequestHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) { From 084674c37d24942b7081b3ea9c96c4a086ee74a1 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Fri, 29 Jul 2022 11:13:48 -0400 Subject: [PATCH 4/9] add version info if info is present --- internal/langserver/handlers/command/terraform.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/langserver/handlers/command/terraform.go b/internal/langserver/handlers/command/terraform.go index 277cea1c2..95ccd17ca 100644 --- a/internal/langserver/handlers/command/terraform.go +++ b/internal/langserver/handlers/command/terraform.go @@ -48,17 +48,15 @@ func (h *CmdHandler) TerraformVersionRequestHandler(ctx context.Context, args cm return response, nil } - progress.Report(ctx, "Module info found ...") - if mod.TerraformVersion == nil { - return response, nil + progress.Report(ctx, "Recording terraform version info ...") + if mod.TerraformVersion != nil { + response.DiscoveredVersion = mod.TerraformVersion.String() } - if mod.Meta.CoreRequirements == nil { - return response, nil + if mod.Meta.CoreRequirements != nil { + response.RequiredVersion = mod.Meta.CoreRequirements.String() } - progress.Report(ctx, "Recording terraform version info ...") - response.DiscoveredVersion = mod.TerraformVersion.String() - response.RequiredVersion = mod.Meta.CoreRequirements.String() + progress.Report(ctx, "Sending response ...") return response, nil } From 3715ac610b65199553be45b26c72720a8e281dcf Mon Sep 17 00:00:00 2001 From: James Pogran Date: Fri, 29 Jul 2022 12:18:49 -0400 Subject: [PATCH 5/9] add test --- .../execute_command_terraform_version_test.go | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 internal/langserver/handlers/execute_command_terraform_version_test.go diff --git a/internal/langserver/handlers/execute_command_terraform_version_test.go b/internal/langserver/handlers/execute_command_terraform_version_test.go new file mode 100644 index 000000000..632c6edfe --- /dev/null +++ b/internal/langserver/handlers/execute_command_terraform_version_test.go @@ -0,0 +1,95 @@ +package handlers + +import ( + "fmt" + "testing" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-ls/internal/document" + "github.com/hashicorp/terraform-ls/internal/langserver" + "github.com/hashicorp/terraform-ls/internal/langserver/cmd" + "github.com/hashicorp/terraform-ls/internal/state" + "github.com/hashicorp/terraform-ls/internal/terraform/exec" + "github.com/hashicorp/terraform-ls/internal/uri" + "github.com/hashicorp/terraform-ls/internal/walker" + tfaddr "github.com/hashicorp/terraform-registry-address" + tfmod "github.com/hashicorp/terraform-schema/module" + "github.com/stretchr/testify/mock" +) + +func TestLangServer_workspaceExecuteCommand_terraformVersion_basic(t *testing.T) { + modDir := t.TempDir() + modUri := uri.FromPath(modDir) + + s, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + + err = s.Modules.Add(modDir) + if err != nil { + t.Fatal(err) + } + + metadata := &tfmod.Meta{ + Path: modDir, + CoreRequirements: testConstraint(t, "~> 0.15"), + } + + err = s.Modules.UpdateMetadata(modDir, metadata, nil) + if err != nil { + t.Fatal(err) + } + + ver, err := version.NewVersion("1.1.0") + if err != nil { + t.Fatal(err) + } + + err = s.Modules.UpdateTerraformVersion(modDir, ver, map[tfaddr.Provider]*version.Version{}, nil) + if err != nil { + t.Fatal(err) + } + + wc := walker.NewWalkerCollector() + + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + TerraformCalls: &exec.TerraformMockCalls{ + PerWorkDir: map[string][]*mock.Call{ + modDir: validTfMockCalls(), + }, + }, + StateStore: s, + WalkerCollector: wc, + })) + stop := ls.Start(t) + defer stop() + + ls.Call(t, &langserver.CallRequest{ + Method: "initialize", + ReqParams: fmt.Sprintf(`{ + "capabilities": {}, + "rootUri": %q, + "processId": 12345 + }`, modUri)}) + waitForWalkerPath(t, s, wc, document.DirHandleFromURI(modUri)) + ls.Notify(t, &langserver.CallRequest{ + Method: "initialized", + ReqParams: "{}", + }) + + ls.CallAndExpectResponse(t, &langserver.CallRequest{ + Method: "workspace/executeCommand", + ReqParams: fmt.Sprintf(`{ + "command": %q, + "arguments": ["uri=%s"] + }`, cmd.Name("module.terraform"), modUri)}, `{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "v": 0, + "required_version": "~\u003e 0.15", + "discovered_version": "1.1.0" + } + }`) +} From 246d7fbfc61587e165a5da124fd9980b8acf3cc9 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 1 Aug 2022 10:43:33 -0400 Subject: [PATCH 6/9] add terraform version change notification --- internal/langserver/handlers/initialize.go | 4 ++++ internal/langserver/handlers/service.go | 4 ++++ internal/protocol/experimental.go | 16 +++++++++++++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/internal/langserver/handlers/initialize.go b/internal/langserver/handlers/initialize.go index f20c4f2f8..8e585cf1a 100644 --- a/internal/langserver/handlers/initialize.go +++ b/internal/langserver/handlers/initialize.go @@ -61,6 +61,10 @@ func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams) expServerCaps.RefreshModuleCalls = true properties["experimentalCapabilities.refreshModuleCalls"] = true } + if _, ok := expClientCaps.RefreshTerraformVersionCommandId(); ok { + expServerCaps.RefreshTerraformVersion = true + properties["experimentalCapabilities.refreshTerraformVersion"] = true + } serverCaps.Capabilities.Experimental = expServerCaps diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index d0dccdd8e..64bd34d3d 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -463,6 +463,10 @@ func (svc *service) configureSessionDependencies(ctx context.Context, cfgOpts *s moduleHooks = append(moduleHooks, callRefreshClientCommand(svc.server, commandId)) } + if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).RefreshTerraformVersionCommandId(); ok { + moduleHooks = append(moduleHooks, callRefreshClientCommand(svc.server, commandId)) + } + if cc.Workspace.SemanticTokens.RefreshSupport { moduleHooks = append(moduleHooks, refreshSemanticTokens(svc.server)) } diff --git a/internal/protocol/experimental.go b/internal/protocol/experimental.go index 07cd12a60..5a596913f 100644 --- a/internal/protocol/experimental.go +++ b/internal/protocol/experimental.go @@ -1,9 +1,10 @@ package protocol type ExperimentalServerCapabilities struct { - ReferenceCountCodeLens bool `json:"referenceCountCodeLens"` - RefreshModuleProviders bool `json:"refreshModuleProviders"` - RefreshModuleCalls bool `json:"refreshModuleCalls"` + ReferenceCountCodeLens bool `json:"referenceCountCodeLens"` + RefreshModuleProviders bool `json:"refreshModuleProviders"` + RefreshModuleCalls bool `json:"refreshModuleCalls"` + RefreshTerraformVersion bool `json:"refreshTerraformVersion"` } type ExpClientCapabilities map[string]interface{} @@ -42,6 +43,15 @@ func (cc ExpClientCapabilities) RefreshModuleCallsCommandId() (string, bool) { return cmdId, ok } +func (cc ExpClientCapabilities) RefreshTerraformVersionCommandId() (string, bool) { + if cc == nil { + return "", false + } + + cmdId, ok := cc["refreshTerraformVersionCommandId"].(string) + return cmdId, ok +} + func (cc ExpClientCapabilities) TelemetryVersion() (int, bool) { if cc == nil { return 0, false From a1ef3217d4857ffba38cbfeec9938a48b401c14f Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 1 Aug 2022 11:00:48 -0400 Subject: [PATCH 7/9] Docs --- docs/commands.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/commands.md b/docs/commands.md index 349777d77..7ca975c20 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -183,3 +183,25 @@ installed version. } } ``` + +### `module.terraform` + +Provides information about the terraform binary version for the current module. + +**Arguments:** + + - `uri` - URI of the directory of the module in question, e.g. `file:///path/to/network` + +**Outputs:** + + - `v` - describes version of the format; Will be used in the future to communicate format changes. + - `required_version` - Version constraint specified in configuration + - `discovered_version` - Version discovered from `terraform version --json` using the `uri` given + +```json +{ + "v": 0, + "required_version": "~> 0.15", + "discovered_version": "1.1.0" +} +``` From 81e7b96847f394ba425a104461aa66bec21c0e03 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 1 Aug 2022 11:22:32 -0400 Subject: [PATCH 8/9] update intialization tests --- internal/langserver/handlers/handlers_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/langserver/handlers/handlers_test.go b/internal/langserver/handlers/handlers_test.go index 2539ce704..db9c9fd6d 100644 --- a/internal/langserver/handlers/handlers_test.go +++ b/internal/langserver/handlers/handlers_test.go @@ -75,7 +75,8 @@ func initializeResponse(t *testing.T, commandPrefix string) string { "experimental": { "referenceCountCodeLens": false, "refreshModuleProviders": false, - "refreshModuleCalls": false + "refreshModuleCalls": false, + "refreshTerraformVersion": false } }, "serverInfo": { From 1c8b9f63e4e52e4aae4c8a069abf21a407886b07 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Tue, 2 Aug 2022 09:20:14 -0400 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: Radek Simko --- docs/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index 7ca975c20..791738169 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -196,7 +196,7 @@ Provides information about the terraform binary version for the current module. - `v` - describes version of the format; Will be used in the future to communicate format changes. - `required_version` - Version constraint specified in configuration - - `discovered_version` - Version discovered from `terraform version --json` using the `uri` given + - `discovered_version` - Version discovered from `terraform version --json` in the directory specified in `uri` ```json {