From 70f612df659f12c6fa59d476ecc585958cdab033 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Tue, 5 Jul 2022 11:25:37 +0200 Subject: [PATCH 01/10] udpate terraform-schema to 1a218af --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d1aa4cdf..d1d1190a 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index f5f6c2ee..356caee5 100644 --- a/go.sum +++ b/go.sum @@ -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= From a7f99923cd027dcec43fcca0d4cd5e40b22453f6 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Tue, 5 Jul 2022 11:26:49 +0200 Subject: [PATCH 02/10] Infer module type from string instead of *ModuleRecord --- internal/terraform/datadir/module_types.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/internal/terraform/datadir/module_types.go b/internal/terraform/datadir/module_types.go index 1511d6d8..9c3ca3b6 100644 --- a/internal/terraform/datadir/module_types.go +++ b/internal/terraform/datadir/module_types.go @@ -4,6 +4,7 @@ import ( "strings" tfaddr "github.com/hashicorp/terraform-registry-address" + tfmod "github.com/hashicorp/terraform-schema/module" ) type ModuleType string @@ -16,36 +17,29 @@ const ( GIT ModuleType = "git" ) -var moduleSourceLocalPrefixes = []string{ - "./", - "../", - ".\\", - "..\\", -} - // GetModuleType parses 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 (r *ModuleRecord) GetModuleType() ModuleType { +func GetModuleType(sourceAddr string) ModuleType { // Example: terraform-aws-modules/ec2-instance/aws // Example: registry.terraform.io/terraform-aws-modules/vpc/aws - moduleSourceRegistry, err := tfaddr.ParseModuleSource(r.SourceAddr) + moduleSourceRegistry, err := tfaddr.ParseModuleSource(sourceAddr) if err == nil && moduleSourceRegistry.Package.Host == "registry.terraform.io" { return TFREGISTRY } // Example: github.com/terraform-aws-modules/terraform-aws-security-group - if strings.HasPrefix(r.SourceAddr, "github.com/") { + if strings.HasPrefix(sourceAddr, "github.com/") { return GITHUB } // Example: git::https://example.com/vpc.git - if strings.HasPrefix(r.SourceAddr, "git::") { + if strings.HasPrefix(sourceAddr, "git::") { return GIT } // Local file paths - if isModuleSourceLocal(r.SourceAddr) { + if isModuleSourceLocal(sourceAddr) { return LOCAL } @@ -53,7 +47,7 @@ func (r *ModuleRecord) GetModuleType() ModuleType { } func isModuleSourceLocal(raw string) bool { - for _, prefix := range moduleSourceLocalPrefixes { + for _, prefix := range tfmod.ModuleSourceLocalPrefixes { if strings.HasPrefix(raw, prefix) { return true } From 0c2c15c5129d7dc4fed59a6fddf1a9301ea7d2f5 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Tue, 5 Jul 2022 12:20:36 +0200 Subject: [PATCH 03/10] 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. --- .../handlers/command/module_calls.go | 70 ++++++------------- 1 file changed, 20 insertions(+), 50 deletions(-) diff --git a/internal/langserver/handlers/command/module_calls.go b/internal/langserver/handlers/command/module_calls.go index 0667bd5c..f271d22e 100644 --- a/internal/langserver/handlers/command/module_calls.go +++ b/internal/langserver/handlers/command/module_calls.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-ls/internal/langserver/cmd" "github.com/hashicorp/terraform-ls/internal/terraform/datadir" "github.com/hashicorp/terraform-ls/internal/uri" + tfmod "github.com/hashicorp/terraform-schema/module" ) const moduleCallsVersion = 0 @@ -25,7 +26,7 @@ type moduleCall struct { Version string `json:"version,omitempty"` SourceType datadir.ModuleType `json:"source_type,omitempty"` DocsLink string `json:"docs_link,omitempty"` - DependentModules []moduleCall `json:"dependent_modules"` + DependentModules []moduleCall `json:"dependent_modules"` // will always be an empty list, we keep this for compatibility } func (h *CmdHandler) ModuleCallsHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) { @@ -48,48 +49,26 @@ 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 _, manifest := range moduleCalls.Declared { + if manifest.SourceAddr == nil { + // We skip all modules without a source address continue } - moduleName := manifest.Key - subModuleName := "" - - // 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) + moduleName := manifest.LocalName + docsLink, err := getModuleDocumentationLink(ctx, manifest.SourceAddr.String(), manifest.Version.String()) if err != nil { h.Logger.Printf("failed to get module docs link: %s", err) } @@ -97,23 +76,14 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, records []datadir.M // build what we know moduleInfo := moduleCall{ Name: moduleName, - SourceAddr: manifest.SourceAddr, + SourceAddr: manifest.SourceAddr.String(), DocsLink: docsLink, - Version: manifest.VersionStr, - SourceType: manifest.GetModuleType(), + Version: manifest.Version.String(), + SourceType: datadir.GetModuleType(manifest.SourceAddr.String()), 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 @@ -129,14 +99,14 @@ 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 string, version string) (string, error) { + if datadir.GetModuleType(sourceAddr) != datadir.TFREGISTRY { return "", nil } - shortName := strings.TrimPrefix(record.SourceAddr, "registry.terraform.io/") + shortName := strings.TrimPrefix(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/%s`, shortName, version) u, err := docsURL(ctx, rawURL, "workspace/executeCommand/module.calls") if err != nil { From 4fa9304a08e943dca555f7a9a9a3197f2c7d331d Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 6 Jul 2022 12:03:21 +0200 Subject: [PATCH 04/10] update module.calls tests --- .../handlers/command/module_calls_test.go | 113 +++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/internal/langserver/handlers/command/module_calls_test.go b/internal/langserver/handlers/command/module_calls_test.go index 9bc380d5..78565baa 100644 --- a/internal/langserver/handlers/command/module_calls_test.go +++ b/internal/langserver/handlers/command/module_calls_test.go @@ -5,68 +5,69 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-ls/internal/terraform/datadir" + "github.com/hashicorp/go-version" + tfaddr "github.com/hashicorp/terraform-registry-address" + "github.com/hashicorp/terraform-schema/module" + tfmod "github.com/hashicorp/terraform-schema/module" ) func Test_parseModuleRecords(t *testing.T) { tests := []struct { - name string - records []datadir.ModuleRecord - want []moduleCall + name string + moduleCalls tfmod.ModuleCalls + want []moduleCall }{ { name: "detects terraform module types", - records: []datadir.ModuleRecord{ - { - Key: "ec2_instances", - SourceAddr: "terraform-aws-modules/ec2-instance/aws", - VersionStr: "2.12.0", - Dir: ".terraform\\modules\\ec2_instances", - }, - { - Key: "web_server_sg", - SourceAddr: "github.com/terraform-aws-modules/terraform-aws-security-group", - VersionStr: "", - Dir: ".terraform\\modules\\web_server_sg", - }, - { - Key: "eks", - SourceAddr: "terraform-aws-modules/eks/aws", - VersionStr: "17.20.0", - Dir: ".terraform\\modules\\eks", - }, - { - Key: "eks.fargate", - SourceAddr: "./modules/fargate", - VersionStr: "", - Dir: ".terraform\\modules\\eks\\modules\\fargate", + moduleCalls: tfmod.ModuleCalls{ + Installed: map[string]tfmod.InstalledModuleCall{}, + Declared: map[string]tfmod.DeclaredModuleCall{ + "ec2_instances": { + LocalName: "ec2_instances", + SourceAddr: tfaddr.MustParseModuleSource("terraform-aws-modules/ec2-instance/aws"), + Version: version.MustConstraints(version.NewConstraint("2.12.0")), + }, + "web_server_sg": { + LocalName: "web_server_sg", + SourceAddr: module.UnknownSourceAddr("github.com/terraform-aws-modules/terraform-aws-security-group"), + Version: nil, + }, + "eks": { + LocalName: "eks", + SourceAddr: tfaddr.MustParseModuleSource("terraform-aws-modules/eks/aws"), + Version: version.MustConstraints(version.NewConstraint("17.20.0")), + }, + "beta": { + LocalName: "beta", + SourceAddr: module.LocalSourceAddr("./beta"), + Version: nil, + }, }, }, want: []moduleCall{ + { + Name: "beta", + SourceAddr: "./beta", + Version: "", + SourceType: "local", + DocsLink: "", + DependentModules: []moduleCall{}, + }, { Name: "ec2_instances", - SourceAddr: "terraform-aws-modules/ec2-instance/aws", + SourceAddr: "registry.terraform.io/terraform-aws-modules/ec2-instance/aws", Version: "2.12.0", SourceType: "tfregistry", DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/2.12.0?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", DependentModules: []moduleCall{}, }, { - Name: "eks", - SourceAddr: "terraform-aws-modules/eks/aws", - Version: "17.20.0", - SourceType: "tfregistry", - DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/17.20.0?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", - DependentModules: []moduleCall{ - { - Name: "fargate", - SourceAddr: "./modules/fargate", - Version: "", - SourceType: "local", - DocsLink: "", - DependentModules: []moduleCall{}, - }, - }, + Name: "eks", + SourceAddr: "registry.terraform.io/terraform-aws-modules/eks/aws", + Version: "17.20.0", + SourceType: "tfregistry", + DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/17.20.0?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", + DependentModules: []moduleCall{}, }, { Name: "web_server_sg", @@ -83,7 +84,7 @@ func Test_parseModuleRecords(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() h := &CmdHandler{} - got := h.parseModuleRecords(ctx, tt.records) + got := h.parseModuleRecords(ctx, tt.moduleCalls) if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("module mismatch: %s", diff) } @@ -94,18 +95,20 @@ func Test_parseModuleRecords(t *testing.T) { // With the release of Terraform 1.1.0 module source addresses are now stored normalized func Test_parseModuleRecords_v1_1(t *testing.T) { tests := []struct { - name string - records []datadir.ModuleRecord - want []moduleCall + name string + moduleCalls tfmod.ModuleCalls + want []moduleCall }{ { name: "detects terraform module types", - records: []datadir.ModuleRecord{ - { - Key: "ec2_instances", - SourceAddr: "registry.terraform.io/terraform-aws-modules/ec2-instance/aws", - VersionStr: "2.12.0", - Dir: ".terraform\\modules\\ec2_instances", + moduleCalls: tfmod.ModuleCalls{ + Installed: map[string]tfmod.InstalledModuleCall{}, + Declared: map[string]tfmod.DeclaredModuleCall{ + "ec2_instances": { + LocalName: "ec2_instances", + SourceAddr: tfaddr.MustParseModuleSource("registry.terraform.io/terraform-aws-modules/ec2-instance/aws"), + Version: version.MustConstraints(version.NewConstraint("2.12.0")), + }, }, }, want: []moduleCall{ @@ -124,7 +127,7 @@ func Test_parseModuleRecords_v1_1(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() h := &CmdHandler{} - got := h.parseModuleRecords(ctx, tt.records) + got := h.parseModuleRecords(ctx, tt.moduleCalls) if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("module mismatch: %s", diff) } From d9d44c8a04f860871525bc882d1cdfa821268b8f Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 6 Jul 2022 14:30:16 +0200 Subject: [PATCH 05/10] update module.calls docs --- docs/commands.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 867200bb..34aac31b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -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:** @@ -127,7 +126,7 @@ The data is sourced from the local cache within `.terraform` and so it may not n - `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* - array of dependent modules with the same structure as `module_calls` ```json { From e620dc176f66f8342c180da2353ceee4385140fb Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 6 Jul 2022 16:01:25 +0200 Subject: [PATCH 06/10] refactor getModuleType --- .../handlers/command/module_calls.go | 72 +++++++++++++++---- internal/terraform/datadir/module_types.go | 56 --------------- 2 files changed, 57 insertions(+), 71 deletions(-) delete mode 100644 internal/terraform/datadir/module_types.go diff --git a/internal/langserver/handlers/command/module_calls.go b/internal/langserver/handlers/command/module_calls.go index f271d22e..3a42f853 100644 --- a/internal/langserver/handlers/command/module_calls.go +++ b/internal/langserver/handlers/command/module_calls.go @@ -8,8 +8,9 @@ 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" ) @@ -21,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"` // will always be an empty list, we keep this for compatibility + 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, @@ -68,9 +79,15 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.M } moduleName := manifest.LocalName - docsLink, err := getModuleDocumentationLink(ctx, manifest.SourceAddr.String(), manifest.Version.String()) - if err != nil { - h.Logger.Printf("failed to get module docs link: %s", err) + sourceType := getModuleType(manifest.SourceAddr) + + docsLink := "" + if sourceType == TFREGISTRY { + var err error + docsLink, err = getModuleDocumentationLink(ctx, manifest.SourceAddr.String(), manifest.Version.String()) + if err != nil { + h.Logger.Printf("failed to get module docs link: %s", err) + } } // build what we know @@ -79,7 +96,7 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.M SourceAddr: manifest.SourceAddr.String(), DocsLink: docsLink, Version: manifest.Version.String(), - SourceType: datadir.GetModuleType(manifest.SourceAddr.String()), + SourceType: sourceType, DependentModules: make([]moduleCall, 0), } @@ -100,10 +117,6 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.M } func getModuleDocumentationLink(ctx context.Context, sourceAddr string, version string) (string, error) { - if datadir.GetModuleType(sourceAddr) != datadir.TFREGISTRY { - return "", nil - } - shortName := strings.TrimPrefix(sourceAddr, "registry.terraform.io/") rawURL := fmt.Sprintf(`https://registry.terraform.io/modules/%s/%s`, shortName, version) @@ -115,3 +128,32 @@ func getModuleDocumentationLink(ctx context.Context, sourceAddr string, version 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 +} diff --git a/internal/terraform/datadir/module_types.go b/internal/terraform/datadir/module_types.go deleted file mode 100644 index 9c3ca3b6..00000000 --- a/internal/terraform/datadir/module_types.go +++ /dev/null @@ -1,56 +0,0 @@ -package datadir - -import ( - "strings" - - tfaddr "github.com/hashicorp/terraform-registry-address" - tfmod "github.com/hashicorp/terraform-schema/module" -) - -type ModuleType string - -const ( - UNKNOWN ModuleType = "unknown" - TFREGISTRY ModuleType = "tfregistry" - LOCAL ModuleType = "local" - GITHUB ModuleType = "github" - GIT ModuleType = "git" -) - -// GetModuleType parses 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 string) ModuleType { - // Example: terraform-aws-modules/ec2-instance/aws - // Example: registry.terraform.io/terraform-aws-modules/vpc/aws - moduleSourceRegistry, err := tfaddr.ParseModuleSource(sourceAddr) - if err == nil && moduleSourceRegistry.Package.Host == "registry.terraform.io" { - return TFREGISTRY - } - - // Example: github.com/terraform-aws-modules/terraform-aws-security-group - if strings.HasPrefix(sourceAddr, "github.com/") { - return GITHUB - } - - // Example: git::https://example.com/vpc.git - if strings.HasPrefix(sourceAddr, "git::") { - return GIT - } - - // Local file paths - if isModuleSourceLocal(sourceAddr) { - return LOCAL - } - - return UNKNOWN -} - -func isModuleSourceLocal(raw string) bool { - for _, prefix := range tfmod.ModuleSourceLocalPrefixes { - if strings.HasPrefix(raw, prefix) { - return true - } - } - return false -} From 0c76153bac71fd45f91f99b41ab6f7329aabae8b Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 6 Jul 2022 16:02:56 +0200 Subject: [PATCH 07/10] Update docs/commands.md 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 34aac31b..9c59d652 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -126,7 +126,7 @@ The data is sourced from the declared modules inside the files of the module. - `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` - *deprecated* - 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 { From 5b9abba79ac95828709d541974a5235ba8087b78 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Thu, 7 Jul 2022 11:49:21 +0200 Subject: [PATCH 08/10] always link to the latest module version --- internal/langserver/handlers/command/module_calls.go | 6 +++--- internal/langserver/handlers/command/module_calls_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/langserver/handlers/command/module_calls.go b/internal/langserver/handlers/command/module_calls.go index 3a42f853..096ce792 100644 --- a/internal/langserver/handlers/command/module_calls.go +++ b/internal/langserver/handlers/command/module_calls.go @@ -84,7 +84,7 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.M docsLink := "" if sourceType == TFREGISTRY { var err error - docsLink, err = getModuleDocumentationLink(ctx, manifest.SourceAddr.String(), manifest.Version.String()) + docsLink, err = getModuleDocumentationLink(ctx, manifest.SourceAddr.String()) if err != nil { h.Logger.Printf("failed to get module docs link: %s", err) } @@ -116,10 +116,10 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.M return list } -func getModuleDocumentationLink(ctx context.Context, sourceAddr string, version string) (string, error) { +func getModuleDocumentationLink(ctx context.Context, sourceAddr string) (string, error) { shortName := strings.TrimPrefix(sourceAddr, "registry.terraform.io/") - rawURL := fmt.Sprintf(`https://registry.terraform.io/modules/%s/%s`, shortName, version) + rawURL := fmt.Sprintf(`https://registry.terraform.io/modules/%s/latest`, shortName) u, err := docsURL(ctx, rawURL, "workspace/executeCommand/module.calls") if err != nil { diff --git a/internal/langserver/handlers/command/module_calls_test.go b/internal/langserver/handlers/command/module_calls_test.go index 78565baa..0a8b2047 100644 --- a/internal/langserver/handlers/command/module_calls_test.go +++ b/internal/langserver/handlers/command/module_calls_test.go @@ -58,7 +58,7 @@ func Test_parseModuleRecords(t *testing.T) { SourceAddr: "registry.terraform.io/terraform-aws-modules/ec2-instance/aws", Version: "2.12.0", SourceType: "tfregistry", - DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/2.12.0?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", + DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/latest?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", DependentModules: []moduleCall{}, }, { @@ -66,7 +66,7 @@ func Test_parseModuleRecords(t *testing.T) { SourceAddr: "registry.terraform.io/terraform-aws-modules/eks/aws", Version: "17.20.0", SourceType: "tfregistry", - DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/17.20.0?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", + DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", DependentModules: []moduleCall{}, }, { @@ -117,7 +117,7 @@ func Test_parseModuleRecords_v1_1(t *testing.T) { SourceAddr: "registry.terraform.io/terraform-aws-modules/ec2-instance/aws", Version: "2.12.0", SourceType: "tfregistry", - DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/2.12.0?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", + DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/latest?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", DependentModules: []moduleCall{}, }, }, From e15ed9c2bdab17c52a8bc33b531a462c280f5b1c Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 8 Jul 2022 14:14:11 +0200 Subject: [PATCH 09/10] review feedback --- .../handlers/command/module_calls.go | 34 +++++++++---------- .../handlers/command/module_calls_test.go | 4 +++ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/internal/langserver/handlers/command/module_calls.go b/internal/langserver/handlers/command/module_calls.go index 096ce792..1184bdfc 100644 --- a/internal/langserver/handlers/command/module_calls.go +++ b/internal/langserver/handlers/command/module_calls.go @@ -72,30 +72,26 @@ func (h *CmdHandler) ModuleCallsHandler(ctx context.Context, args cmd.CommandArg func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.ModuleCalls) []moduleCall { modules := make(map[string]moduleCall) - for _, manifest := range moduleCalls.Declared { - if manifest.SourceAddr == nil { - // We skip all modules without a source address + for _, module := range moduleCalls.Declared { + if module.SourceAddr == nil { + // We skip all modules with an empty source address continue } - moduleName := manifest.LocalName - sourceType := getModuleType(manifest.SourceAddr) + moduleName := module.LocalName + sourceType := getModuleType(module.SourceAddr) - docsLink := "" - if sourceType == TFREGISTRY { - var err error - docsLink, err = getModuleDocumentationLink(ctx, manifest.SourceAddr.String()) - if err != nil { - h.Logger.Printf("failed to get module docs link: %s", err) - } + 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.String(), + SourceAddr: module.SourceAddr.String(), DocsLink: docsLink, - Version: manifest.Version.String(), + Version: module.Version.String(), SourceType: sourceType, DependentModules: make([]moduleCall, 0), } @@ -116,10 +112,12 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.M return list } -func getModuleDocumentationLink(ctx context.Context, sourceAddr string) (string, error) { - shortName := strings.TrimPrefix(sourceAddr, "registry.terraform.io/") - - rawURL := fmt.Sprintf(`https://registry.terraform.io/modules/%s/latest`, shortName) +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 + } + 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 { diff --git a/internal/langserver/handlers/command/module_calls_test.go b/internal/langserver/handlers/command/module_calls_test.go index 0a8b2047..57e232d6 100644 --- a/internal/langserver/handlers/command/module_calls_test.go +++ b/internal/langserver/handlers/command/module_calls_test.go @@ -42,6 +42,10 @@ func Test_parseModuleRecords(t *testing.T) { SourceAddr: module.LocalSourceAddr("./beta"), Version: nil, }, + "empty": { + LocalName: "empty", + Version: nil, + }, }, }, want: []moduleCall{ From 6d65e1311ac65813a67bee45340e9c8f7296c736 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 8 Jul 2022 17:46:22 +0200 Subject: [PATCH 10/10] Output module calls source addr as human readable --- docs/commands.md | 2 +- internal/langserver/handlers/command/module_calls.go | 2 +- internal/langserver/handlers/command/module_calls_test.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 9c59d652..349777d7 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -122,7 +122,7 @@ The data is sourced from the declared modules inside the files of the module. - `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 diff --git a/internal/langserver/handlers/command/module_calls.go b/internal/langserver/handlers/command/module_calls.go index 1184bdfc..bc47132e 100644 --- a/internal/langserver/handlers/command/module_calls.go +++ b/internal/langserver/handlers/command/module_calls.go @@ -89,7 +89,7 @@ func (h *CmdHandler) parseModuleRecords(ctx context.Context, moduleCalls tfmod.M // build what we know moduleInfo := moduleCall{ Name: moduleName, - SourceAddr: module.SourceAddr.String(), + SourceAddr: module.SourceAddr.ForDisplay(), DocsLink: docsLink, Version: module.Version.String(), SourceType: sourceType, diff --git a/internal/langserver/handlers/command/module_calls_test.go b/internal/langserver/handlers/command/module_calls_test.go index 57e232d6..a3a3abf7 100644 --- a/internal/langserver/handlers/command/module_calls_test.go +++ b/internal/langserver/handlers/command/module_calls_test.go @@ -59,7 +59,7 @@ func Test_parseModuleRecords(t *testing.T) { }, { Name: "ec2_instances", - SourceAddr: "registry.terraform.io/terraform-aws-modules/ec2-instance/aws", + SourceAddr: "terraform-aws-modules/ec2-instance/aws", Version: "2.12.0", SourceType: "tfregistry", DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/latest?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", @@ -67,7 +67,7 @@ func Test_parseModuleRecords(t *testing.T) { }, { Name: "eks", - SourceAddr: "registry.terraform.io/terraform-aws-modules/eks/aws", + SourceAddr: "terraform-aws-modules/eks/aws", Version: "17.20.0", SourceType: "tfregistry", DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls", @@ -118,7 +118,7 @@ func Test_parseModuleRecords_v1_1(t *testing.T) { want: []moduleCall{ { Name: "ec2_instances", - SourceAddr: "registry.terraform.io/terraform-aws-modules/ec2-instance/aws", + SourceAddr: "terraform-aws-modules/ec2-instance/aws", Version: "2.12.0", SourceType: "tfregistry", DocsLink: "https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/latest?utm_content=workspace%2FexecuteCommand%2Fmodule.calls&utm_source=terraform-ls",