Skip to content

Commit

Permalink
Use moduleCalls.Declared as source for module.calls (#987)
Browse files Browse the repository at this point in the history
* udpate terraform-schema to 1a218af

* Infer module type from string instead of *ModuleRecord

* Use `moduleCalls.Declared` as source for `module.calls`

This removes any nesting in the tree view for now. Hierarchy
can be added later by parsing `moduleCalls.Installed` (might be
stale) or a new way of parsing local module sources.

* update module.calls tests

* update module.calls docs

* refactor getModuleType

* Update docs/commands.md

Co-authored-by: Radek Simko <[email protected]>

* always link to the latest module version

* review feedback

* Output module calls source addr as human readable

Co-authored-by: Radek Simko <[email protected]>
  • Loading branch information
dbanck and radeksimko authored Jul 8, 2022
1 parent 81b49a1 commit 60986be
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 183 deletions.
7 changes: 3 additions & 4 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,9 @@ List of modules called by the module under the given URI.
Empty array may be returned when e.g.
- the URI doesn't represent a module
- the configuration is invalid
- modules are not installed
- there are no module calls

The data is sourced from the local cache within `.terraform` and so it may not necessarily represent newly added module calls in the configuration until they're installed via `get` or `init`.
The data is sourced from the declared modules inside the files of the module.

**Arguments:**

Expand All @@ -123,11 +122,11 @@ The data is sourced from the local cache within `.terraform` and so it may not n
- `v` - describes version of the format; Will be used in the future to communicate format changes.
- `module_calls` - array of modules which are called from the module in question
- `name` - the reference name of this particular module (i.e. `network` from `module "network" { ...`)
- `source_addr` - the source address given for this module call (e.g. `terraform-aws-modules/eks/aws`)
- `source_addr` - human-readable version of the source address given for this module call (e.g. `terraform-aws-modules/eks/aws`)
- `version` - version constraint of the module call; applicable to modules hosted by the Terraform Registry (e.g. `~> 1.0`
- `source_type` - source of the Terraform module, e.g. `github` or `tfregistry`
- `docs_link` - a link to the module documentation; if available
- `dependent_modules` - array of dependent modules with the same structure as `module_calls`
- `dependent_modules` - **DEPRECATED** (always empty in `v0.29+`) - array of dependent modules with the same structure as `module_calls`

```json
{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/hashicorp/terraform-exec v0.17.2
github.com/hashicorp/terraform-json v0.14.0
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c
github.com/hashicorp/terraform-schema v0.0.0-20220630150034-e05b3dd5db1d
github.com/hashicorp/terraform-schema v0.0.0-20220708120958-1a218af064d0
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5
github.com/mitchellh/cli v1.1.4
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,8 @@ github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e
github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
github.com/hashicorp/terraform-schema v0.0.0-20220630150034-e05b3dd5db1d h1:xqSnkGQIzastFxbk0asDC3pcbvpaPbfF7IVsm1h3pUM=
github.com/hashicorp/terraform-schema v0.0.0-20220630150034-e05b3dd5db1d/go.mod h1:GL+zHwXHrTc/MfKnQP8K2Z4hmaTck85ndiIbvZueMng=
github.com/hashicorp/terraform-schema v0.0.0-20220708120958-1a218af064d0 h1:GXmF/3qrN23YiKC/ubb6jOrAkRFs1lFMCTkM811bRZg=
github.com/hashicorp/terraform-schema v0.0.0-20220708120958-1a218af064d0/go.mod h1:GL+zHwXHrTc/MfKnQP8K2Z4hmaTck85ndiIbvZueMng=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
Expand Down
124 changes: 67 additions & 57 deletions internal/langserver/handlers/command/module_calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (

"github.com/creachadair/jrpc2/code"
"github.com/hashicorp/terraform-ls/internal/langserver/cmd"
"github.com/hashicorp/terraform-ls/internal/terraform/datadir"
"github.com/hashicorp/terraform-ls/internal/uri"
tfaddr "github.com/hashicorp/terraform-registry-address"
"github.com/hashicorp/terraform-schema/module"
tfmod "github.com/hashicorp/terraform-schema/module"
)

const moduleCallsVersion = 0
Expand All @@ -20,14 +22,24 @@ type moduleCallsResponse struct {
}

type moduleCall struct {
Name string `json:"name"`
SourceAddr string `json:"source_addr"`
Version string `json:"version,omitempty"`
SourceType datadir.ModuleType `json:"source_type,omitempty"`
DocsLink string `json:"docs_link,omitempty"`
DependentModules []moduleCall `json:"dependent_modules"`
Name string `json:"name"`
SourceAddr string `json:"source_addr"`
Version string `json:"version,omitempty"`
SourceType ModuleType `json:"source_type,omitempty"`
DocsLink string `json:"docs_link,omitempty"`
DependentModules []moduleCall `json:"dependent_modules"` // will always be an empty list, we keep this for compatibility
}

type ModuleType string

const (
UNKNOWN ModuleType = "unknown"
TFREGISTRY ModuleType = "tfregistry"
LOCAL ModuleType = "local"
GITHUB ModuleType = "github"
GIT ModuleType = "git"
)

func (h *CmdHandler) ModuleCallsHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) {
response := moduleCallsResponse{
FormatVersion: moduleCallsVersion,
Expand All @@ -48,72 +60,43 @@ func (h *CmdHandler) ModuleCallsHandler(ctx context.Context, args cmd.CommandArg
return response, err
}

found, _ := h.StateStore.Modules.ModuleByPath(modPath)
if found == nil {
return response, nil
}

if found.ModManifest == nil {
return response, nil
moduleCalls, err := h.StateStore.Modules.ModuleCalls(modPath)
if err != nil {
return response, err
}

response.ModuleCalls = h.parseModuleRecords(ctx, found.ModManifest.Records)
response.ModuleCalls = h.parseModuleRecords(ctx, moduleCalls)

return response, nil
}

func (h *CmdHandler) parseModuleRecords(ctx context.Context, records []datadir.ModuleRecord) []moduleCall {
// sort all records by key so that dependent modules are found
// after primary modules
sort.SliceStable(records, func(i, j int) bool {
return records[i].Key < records[j].Key
})

func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.ModuleCalls) []moduleCall {
modules := make(map[string]moduleCall)
for _, manifest := range records {
if manifest.IsRoot() {
// this is the current directory, which is technically a module
// skipping as it's not relevant in the activity bar (yet?)
for _, module := range moduleCalls.Declared {
if module.SourceAddr == nil {
// We skip all modules with an empty source address
continue
}

moduleName := manifest.Key
subModuleName := ""
moduleName := module.LocalName
sourceType := getModuleType(module.SourceAddr)

// determine if this module is nested in another module
// in the current workspace by finding a period in the moduleName
// is it better to look at SourceAddr and compare?
if strings.Contains(manifest.Key, ".") {
v := strings.Split(manifest.Key, ".")
moduleName = v[0]
subModuleName = v[1]
}

docsLink, err := getModuleDocumentationLink(ctx, manifest)
docsLink, err := getModuleDocumentationLink(ctx, module.SourceAddr)
if err != nil {
h.Logger.Printf("failed to get module docs link: %s", err)
}

// build what we know
moduleInfo := moduleCall{
Name: moduleName,
SourceAddr: manifest.SourceAddr,
SourceAddr: module.SourceAddr.ForDisplay(),
DocsLink: docsLink,
Version: manifest.VersionStr,
SourceType: manifest.GetModuleType(),
Version: module.Version.String(),
SourceType: sourceType,
DependentModules: make([]moduleCall, 0),
}

m, present := modules[moduleName]
if present {
// this module is located inside another so append
moduleInfo.Name = subModuleName
m.DependentModules = append(m.DependentModules, moduleInfo)
modules[moduleName] = m
} else {
// this is the first we've seen module
modules[moduleName] = moduleInfo
}
modules[moduleName] = moduleInfo
}

// don't need the map anymore, return a list of modules found
Expand All @@ -129,14 +112,12 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, records []datadir.M
return list
}

func getModuleDocumentationLink(ctx context.Context, record datadir.ModuleRecord) (string, error) {
if record.GetModuleType() != datadir.TFREGISTRY {
func getModuleDocumentationLink(ctx context.Context, sourceAddr tfmod.ModuleSourceAddr) (string, error) {
registryAddr, ok := sourceAddr.(tfaddr.Module)
if !ok || registryAddr.Package.Host != "registry.terraform.io" {
return "", nil
}

shortName := strings.TrimPrefix(record.SourceAddr, "registry.terraform.io/")

rawURL := fmt.Sprintf(`https://registry.terraform.io/modules/%s/%s`, shortName, record.VersionStr)
rawURL := fmt.Sprintf(`https://registry.terraform.io/modules/%s/latest`, registryAddr.Package.ForRegistryProtocol())

u, err := docsURL(ctx, rawURL, "workspace/executeCommand/module.calls")
if err != nil {
Expand All @@ -145,3 +126,32 @@ func getModuleDocumentationLink(ctx context.Context, record datadir.ModuleRecord

return u.String(), nil
}

// GetModuleType checks source addresses to determine what kind of source the Terraform module comes
// from. It currently supports detecting Terraform Registry modules, GitHub modules, Git modules, and
// local file paths
func getModuleType(sourceAddr tfmod.ModuleSourceAddr) ModuleType {
// Example: terraform-aws-modules/ec2-instance/aws
// Example: registry.terraform.io/terraform-aws-modules/vpc/aws
_, ok := sourceAddr.(tfaddr.Module)
if ok {
return TFREGISTRY
}

_, ok = sourceAddr.(module.LocalSourceAddr)
if ok {
return LOCAL
}

// Example: github.com/terraform-aws-modules/terraform-aws-security-group
if strings.HasPrefix(sourceAddr.String(), "github.com/") {
return GITHUB
}

// Example: git::https://example.com/vpc.git
if strings.HasPrefix(sourceAddr.String(), "git::") {
return GIT
}

return UNKNOWN
}
Loading

0 comments on commit 60986be

Please sign in to comment.