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

Use moduleCalls.Declared as source for module.calls #987

Merged
merged 10 commits into from
Jul 8, 2022
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: 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.1
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