Skip to content

Commit 1d16fc6

Browse files
authored
Add terraform version command (#1016)
This commit adds a new `module.terraform` command which returns the required and discovered version of Terraform in the current workspace.
1 parent 51c88cc commit 1d16fc6

8 files changed

+203
-4
lines changed

docs/commands.md

+22
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,25 @@ installed version.
183183
}
184184
}
185185
```
186+
187+
### `module.terraform`
188+
189+
Provides information about the terraform binary version for the current module.
190+
191+
**Arguments:**
192+
193+
- `uri` - URI of the directory of the module in question, e.g. `file:///path/to/network`
194+
195+
**Outputs:**
196+
197+
- `v` - describes version of the format; Will be used in the future to communicate format changes.
198+
- `required_version` - Version constraint specified in configuration
199+
- `discovered_version` - Version discovered from `terraform version --json` in the directory specified in `uri`
200+
201+
```json
202+
{
203+
"v": 0,
204+
"required_version": "~> 0.15",
205+
"discovered_version": "1.1.0"
206+
}
207+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package command
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/creachadair/jrpc2/code"
8+
"github.com/hashicorp/terraform-ls/internal/langserver/cmd"
9+
"github.com/hashicorp/terraform-ls/internal/langserver/progress"
10+
"github.com/hashicorp/terraform-ls/internal/uri"
11+
)
12+
13+
const terraformVersionRequestVersion = 0
14+
15+
type terraformInfoResponse struct {
16+
FormatVersion int `json:"v"`
17+
RequiredVersion string `json:"required_version,omitempty"`
18+
DiscoveredVersion string `json:"discovered_version,omitempty"`
19+
}
20+
21+
func (h *CmdHandler) TerraformVersionRequestHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) {
22+
progress.Begin(ctx, "Initializing")
23+
defer func() {
24+
progress.End(ctx, "Finished")
25+
}()
26+
27+
response := terraformInfoResponse{
28+
FormatVersion: terraformVersionRequestVersion,
29+
}
30+
31+
progress.Report(ctx, "Finding current module info ...")
32+
modUri, ok := args.GetString("uri")
33+
if !ok || modUri == "" {
34+
return response, fmt.Errorf("%w: expected module uri argument to be set", code.InvalidParams.Err())
35+
}
36+
37+
if !uri.IsURIValid(modUri) {
38+
return response, fmt.Errorf("URI %q is not valid", modUri)
39+
}
40+
41+
modPath, err := uri.PathFromURI(modUri)
42+
if err != nil {
43+
return response, err
44+
}
45+
46+
mod, _ := h.StateStore.Modules.ModuleByPath(modPath)
47+
if mod == nil {
48+
return response, nil
49+
}
50+
51+
progress.Report(ctx, "Recording terraform version info ...")
52+
if mod.TerraformVersion != nil {
53+
response.DiscoveredVersion = mod.TerraformVersion.String()
54+
}
55+
if mod.Meta.CoreRequirements != nil {
56+
response.RequiredVersion = mod.Meta.CoreRequirements.String()
57+
}
58+
59+
progress.Report(ctx, "Sending response ...")
60+
61+
return response, nil
62+
}

internal/langserver/handlers/execute_command.go

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func cmdHandlers(svc *service) cmd.Handlers {
2424
cmd.Name("terraform.validate"): cmdHandler.TerraformValidateHandler,
2525
cmd.Name("module.calls"): cmdHandler.ModuleCallsHandler,
2626
cmd.Name("module.providers"): cmdHandler.ModuleProvidersHandler,
27+
cmd.Name("module.terraform"): cmdHandler.TerraformVersionRequestHandler,
2728
}
2829
}
2930

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package handlers
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/go-version"
8+
"github.com/hashicorp/terraform-ls/internal/document"
9+
"github.com/hashicorp/terraform-ls/internal/langserver"
10+
"github.com/hashicorp/terraform-ls/internal/langserver/cmd"
11+
"github.com/hashicorp/terraform-ls/internal/state"
12+
"github.com/hashicorp/terraform-ls/internal/terraform/exec"
13+
"github.com/hashicorp/terraform-ls/internal/uri"
14+
"github.com/hashicorp/terraform-ls/internal/walker"
15+
tfaddr "github.com/hashicorp/terraform-registry-address"
16+
tfmod "github.com/hashicorp/terraform-schema/module"
17+
"github.com/stretchr/testify/mock"
18+
)
19+
20+
func TestLangServer_workspaceExecuteCommand_terraformVersion_basic(t *testing.T) {
21+
modDir := t.TempDir()
22+
modUri := uri.FromPath(modDir)
23+
24+
s, err := state.NewStateStore()
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
29+
err = s.Modules.Add(modDir)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
metadata := &tfmod.Meta{
35+
Path: modDir,
36+
CoreRequirements: testConstraint(t, "~> 0.15"),
37+
}
38+
39+
err = s.Modules.UpdateMetadata(modDir, metadata, nil)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
ver, err := version.NewVersion("1.1.0")
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
49+
err = s.Modules.UpdateTerraformVersion(modDir, ver, map[tfaddr.Provider]*version.Version{}, nil)
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
54+
wc := walker.NewWalkerCollector()
55+
56+
ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
57+
TerraformCalls: &exec.TerraformMockCalls{
58+
PerWorkDir: map[string][]*mock.Call{
59+
modDir: validTfMockCalls(),
60+
},
61+
},
62+
StateStore: s,
63+
WalkerCollector: wc,
64+
}))
65+
stop := ls.Start(t)
66+
defer stop()
67+
68+
ls.Call(t, &langserver.CallRequest{
69+
Method: "initialize",
70+
ReqParams: fmt.Sprintf(`{
71+
"capabilities": {},
72+
"rootUri": %q,
73+
"processId": 12345
74+
}`, modUri)})
75+
waitForWalkerPath(t, s, wc, document.DirHandleFromURI(modUri))
76+
ls.Notify(t, &langserver.CallRequest{
77+
Method: "initialized",
78+
ReqParams: "{}",
79+
})
80+
81+
ls.CallAndExpectResponse(t, &langserver.CallRequest{
82+
Method: "workspace/executeCommand",
83+
ReqParams: fmt.Sprintf(`{
84+
"command": %q,
85+
"arguments": ["uri=%s"]
86+
}`, cmd.Name("module.terraform"), modUri)}, `{
87+
"jsonrpc": "2.0",
88+
"id": 2,
89+
"result": {
90+
"v": 0,
91+
"required_version": "~\u003e 0.15",
92+
"discovered_version": "1.1.0"
93+
}
94+
}`)
95+
}

internal/langserver/handlers/handlers_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ func initializeResponse(t *testing.T, commandPrefix string) string {
7676
"experimental": {
7777
"referenceCountCodeLens": false,
7878
"refreshModuleProviders": false,
79-
"refreshModuleCalls": false
79+
"refreshModuleCalls": false,
80+
"refreshTerraformVersion": false
8081
}
8182
},
8283
"serverInfo": {

internal/langserver/handlers/initialize.go

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams)
6161
expServerCaps.RefreshModuleCalls = true
6262
properties["experimentalCapabilities.refreshModuleCalls"] = true
6363
}
64+
if _, ok := expClientCaps.RefreshTerraformVersionCommandId(); ok {
65+
expServerCaps.RefreshTerraformVersion = true
66+
properties["experimentalCapabilities.refreshTerraformVersion"] = true
67+
}
6468

6569
serverCaps.Capabilities.Experimental = expServerCaps
6670

internal/langserver/handlers/service.go

+4
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,10 @@ func (svc *service) configureSessionDependencies(ctx context.Context, cfgOpts *s
476476
moduleHooks = append(moduleHooks, callRefreshClientCommand(svc.server, commandId))
477477
}
478478

479+
if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).RefreshTerraformVersionCommandId(); ok {
480+
moduleHooks = append(moduleHooks, callRefreshClientCommand(svc.server, commandId))
481+
}
482+
479483
if cc.Workspace.SemanticTokens.RefreshSupport {
480484
moduleHooks = append(moduleHooks, refreshSemanticTokens(svc.server))
481485
}

internal/protocol/experimental.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package protocol
22

33
type ExperimentalServerCapabilities struct {
4-
ReferenceCountCodeLens bool `json:"referenceCountCodeLens"`
5-
RefreshModuleProviders bool `json:"refreshModuleProviders"`
6-
RefreshModuleCalls bool `json:"refreshModuleCalls"`
4+
ReferenceCountCodeLens bool `json:"referenceCountCodeLens"`
5+
RefreshModuleProviders bool `json:"refreshModuleProviders"`
6+
RefreshModuleCalls bool `json:"refreshModuleCalls"`
7+
RefreshTerraformVersion bool `json:"refreshTerraformVersion"`
78
}
89

910
type ExpClientCapabilities map[string]interface{}
@@ -42,6 +43,15 @@ func (cc ExpClientCapabilities) RefreshModuleCallsCommandId() (string, bool) {
4243
return cmdId, ok
4344
}
4445

46+
func (cc ExpClientCapabilities) RefreshTerraformVersionCommandId() (string, bool) {
47+
if cc == nil {
48+
return "", false
49+
}
50+
51+
cmdId, ok := cc["refreshTerraformVersionCommandId"].(string)
52+
return cmdId, ok
53+
}
54+
4555
func (cc ExpClientCapabilities) TelemetryVersion() (int, bool) {
4656
if cc == nil {
4757
return 0, false

0 commit comments

Comments
 (0)