Skip to content

Commit

Permalink
Add terraform version command (hashicorp#1016)
Browse files Browse the repository at this point in the history
This commit adds a new `module.terraform` command which returns the required and discovered version of Terraform in the current workspace.
  • Loading branch information
jpogran authored Aug 2, 2022
1 parent 51c88cc commit 1d16fc6
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 4 deletions.
22 changes: 22 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` in the directory specified in `uri`

```json
{
"v": 0,
"required_version": "~> 0.15",
"discovered_version": "1.1.0"
}
```
62 changes: 62 additions & 0 deletions internal/langserver/handlers/command/terraform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package command

import (
"context"
"fmt"

"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"
)

const terraformVersionRequestVersion = 0

type terraformInfoResponse struct {
FormatVersion int `json:"v"`
RequiredVersion string `json:"required_version,omitempty"`
DiscoveredVersion string `json:"discovered_version,omitempty"`
}

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())
}

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
}

progress.Report(ctx, "Recording terraform version info ...")
if mod.TerraformVersion != nil {
response.DiscoveredVersion = mod.TerraformVersion.String()
}
if mod.Meta.CoreRequirements != nil {
response.RequiredVersion = mod.Meta.CoreRequirements.String()
}

progress.Report(ctx, "Sending response ...")

return response, nil
}
1 change: 1 addition & 0 deletions internal/langserver/handlers/execute_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}`)
}
3 changes: 2 additions & 1 deletion internal/langserver/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ func initializeResponse(t *testing.T, commandPrefix string) string {
"experimental": {
"referenceCountCodeLens": false,
"refreshModuleProviders": false,
"refreshModuleCalls": false
"refreshModuleCalls": false,
"refreshTerraformVersion": false
}
},
"serverInfo": {
Expand Down
4 changes: 4 additions & 0 deletions internal/langserver/handlers/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions internal/langserver/handlers/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,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))
}
Expand Down
16 changes: 13 additions & 3 deletions internal/protocol/experimental.go
Original file line number Diff line number Diff line change
@@ -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{}
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 1d16fc6

Please sign in to comment.