Repository: terraform-docs/terraform-docs (go)

Issue:
Support for getting provider version info from dependency lock file
<!--
Thank you for helping to improve terraform-docs!

Please be sure to search for open issues before raising a new one. We use issues
for bug reports and feature requests. Please note, this template is for feature
requests, not bugs report. For more information, see the Contributing Guidelines
at https://git.io/JtEzg. Please find us at https://slack.terraform-docs.io/ for
questions, support, and discussion.
-->

### What problem are you facing?

<!--
Please tell us a little about your use case - it's okay if it's hypothetical!
Leading with this context helps frame the feature request so we can ensure we
implement it sensibly.
--->

Versions do not show up for providers using the new dependency lock file: https://www.terraform.io/docs/language/dependency-lock.html#dependency-installation-behavior



### How could terraform-docs help solve your problem?

<!--
Let us know how you think terraform-docs could help with your use case.
-->

Grab the version from the .terraform.lock.hcl

```none
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/google" {
  version = "3.61.0"
  hashes = [
    "h1:qwvjTMYJPkTvCcEJNNYBpC0RrjyzH5J3UZ4prPXX45o=",
    "zh:09ab9579b5dbb866181d33272eeb53a4aedb70f91f80921b1bc7f9b7b3409671",
    "zh:0c36fad05dad6284746025bab139bbcfd90c5caa20004f49f9ba10b23e353150",
    "zh:1e1a96f2777cd088d3018f5a1df3a420acb9cbc81ade46a454290e4ac5a18a28",
    "zh:2321d64aafde14108ba83249368076a6e756df1268a2e4638ebcbab05c565948",
    "zh:314ce3e4b04a25a438256f6b09c0e9e3e9d1e91e44366c54d914a8bfe488324d",
    "zh:56e71910f66ad71373e00981c9c568cf75e7b844d028d98c3f04a6aca87461b5",
    "zh:65543fc0ad30dd98e551a35c0c9f8d7d5de51ce69c90594f85e03edafbcd56a9",
    "zh:95cd5bec37038aec219b74ec4478eef52143895ddf75bfd796895444f0db0e87",
    "zh:e0852c06803ef9731969631da3189154a6ef9e56087bad763d3c0aa2fe99c0a4",
    "zh:f6a43e7251b3d1fad1958be7ecb99542357648da87f5f0d93f08ac5f4378736a",
  ]
}
```

Pull request description:
Extract provider version from .terraform.lock.hcl
<!--
Thank you for helping to improve terraform-docs!

Please read through https://git.io/JtEzg if this is your first time opening a
terraform-docs pull request. Find us in https://slack.terraform-docs.io/messages/dev if
you need any help contributing.
-->

### Description of your changes

<!--
Briefly describe what this pull request does. Be sure to direct your reviewers'
attention to anything that needs special consideration.

We love pull requests that resolve an open terraform-docs issue. If yours does, you
can uncomment the below line to indicate which issue your PR fixes, for example
"Fixes #500":
-->

If .terraform.lock.hcl exists in module path, try to extract the exact
provider version being used as opposed to provided constraints.

Fixes #443 

I have:

- [x] Read and followed terraform-docs' [contribution process].
- [x] All tests pass when I run `make test`.

### How has this code been tested

<!--
Before reviewers can be confident in the correctness of this pull request, it
needs to tested and shown to be correct. Briefly describe the testing that has
already been done or which is planned for this change.
-->

- built and used against on module with existing `.terraform.lock.hcl`

[contribution process]: https://git.io/JtEzg


Patch:
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
deleted file mode 100644
index 0676c1e..0000000
--- a/.github/workflows/codeql.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: codeql
-
-on:
-  push:
-    branches:
-      - master
-
-env:
-  GO_VERSION: "1.16.4"
-
-jobs:
-  analyze:
-    runs-on: ubuntu-latest
-    if: "!contains(github.event.head_commit.message, '[ci skip]')"
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-
-      - name: Set up Go
-        uses: actions/setup-go@v2
-        with:
-          go-version: ${{ env.GO_VERSION }}
-
-      - name: Initialize CodeQL
-        uses: github/codeql-action/init@v1
-        with:
-          languages: go
-
-      - name: Build binary
-        run: make build
-
-      - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v1
diff --git a/go.mod b/go.mod
index c228d8c..3d53207 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
 	github.com/BurntSushi/toml v0.3.1
 	github.com/hashicorp/go-plugin v1.4.0
 	github.com/hashicorp/go-version v1.3.0
+	github.com/hashicorp/hcl/v2 v2.0.0
 	github.com/iancoleman/orderedmap v0.2.0
 	github.com/imdario/mergo v0.3.11
 	github.com/mitchellh/go-homedir v1.1.0
diff --git a/internal/terraform/module.go b/internal/terraform/module.go
index 2775e33..f820a08 100644
--- a/internal/terraform/module.go
+++ b/internal/terraform/module.go
@@ -23,6 +23,8 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/hashicorp/hcl/v2/hclsimple"
+
 	terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
 	"github.com/terraform-docs/terraform-config-inspect/tfconfig"
 	"github.com/terraform-docs/terraform-docs/internal/reader"
@@ -152,7 +154,7 @@ func loadModuleItems(tfmodule *tfconfig.Module, options *Options) (*Module, erro
 	if err != nil {
 		return nil, err
 	}
-	providers := loadProviders(tfmodule)
+	providers := loadProviders(tfmodule, options)
 	requirements := loadRequirements(tfmodule)
 	resources := loadResources(tfmodule)
 
@@ -398,14 +400,37 @@ func loadOutputValues(options *Options) (map[string]*output, error) {
 	return terraformOutputs, err
 }
 
-func loadProviders(tfmodule *tfconfig.Module) []*Provider {
+func loadProviders(tfmodule *tfconfig.Module, options *Options) []*Provider {
+	type provider struct {
+		Name        string   `hcl:"name,label"`
+		Version     string   `hcl:"version"`
+		Constraints *string  `hcl:"constraints"`
+		Hashes      []string `hcl:"hashes"`
+	}
+	type lockfile struct {
+		Provider []provider `hcl:"provider,block"`
+	}
+	lock := make(map[string]provider)
+	var lf lockfile
+
+	filename := filepath.Join(options.Path, ".terraform.lock.hcl")
+	if err := hclsimple.DecodeFile(filename, nil, &lf); err == nil {
+		for i := range lf.Provider {
+			segments := strings.Split(lf.Provider[i].Name, "/")
+			name := segments[len(segments)-1]
+			lock[name] = lf.Provider[i]
+		}
+	}
+
 	resources := []map[string]*tfconfig.Resource{tfmodule.ManagedResources, tfmodule.DataResources}
 	discovered := make(map[string]*Provider)
 
 	for _, resource := range resources {
 		for _, r := range resource {
 			var version = ""
-			if rv, ok := tfmodule.RequiredProviders[r.Provider.Name]; ok && len(rv.VersionConstraints) > 0 {
+			if l, ok := lock[r.Provider.Name]; ok {
+				version = l.Version
+			} else if rv, ok := tfmodule.RequiredProviders[r.Provider.Name]; ok && len(rv.VersionConstraints) > 0 {
 				version = strings.Join(rv.VersionConstraints, " ")
 			}
 


Test patch:
diff --git a/internal/terraform/module_test.go b/internal/terraform/module_test.go
index c2b9bf6..ad56b27 100644
--- a/internal/terraform/module_test.go
+++ b/internal/terraform/module_test.go
@@ -679,6 +679,13 @@ func TestLoadProviders(t *testing.T) {
 				providers: 3,
 			},
 		},
+		{
+			name: "load module providers from path",
+			path: "with-lock-file",
+			expected: expected{
+				providers: 3,
+			},
+		},
 		{
 			name: "load module providers from path",
 			path: "no-providers",
@@ -690,8 +697,11 @@ func TestLoadProviders(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			assert := assert.New(t)
+			options, _ := NewOptions().With(&Options{
+				Path: tt.path,
+			})
 			module, _ := loadModule(filepath.Join("testdata", tt.path))
-			providers := loadProviders(module)
+			providers := loadProviders(module, options)
 
 			assert.Equal(tt.expected.providers, len(providers))
 		})
diff --git a/internal/terraform/testdata/with-lock-file/.terraform.lock.hcl b/internal/terraform/testdata/with-lock-file/.terraform.lock.hcl
new file mode 100644
index 0000000..03d36f4
--- /dev/null
+++ b/internal/terraform/testdata/with-lock-file/.terraform.lock.hcl
@@ -0,0 +1,57 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+  version     = "3.42.0"
+  constraints = ">= 2.15.0"
+  hashes = [
+    "h1:quV6hK7ewiHWBznGWCb/gJ6JAPm6UtouBUrhAjv6oRY=",
+    "zh:126c856a6eedddd8571f161a826a407ba5655a37a6241393560a96b8c4beca1a",
+    "zh:1a4868e6ac734b5fc2e79a4a889d176286b66664aad709435aa6acee5871d5b0",
+    "zh:40fed7637ab8ddeb93bef06aded35d970f0628025b97459ae805463e8aa0a58a",
+    "zh:68def3c0a5a1aac1db6372c51daef858b707f03052626d3427ac24cba6f2014d",
+    "zh:6db7ec9c8d1803a0b6f40a664aa892e0f8894562de83061fa7ac1bc51ff5e7e5",
+    "zh:7058abaad595930b3f97dc04e45c112b2dbf37d098372a849081f7081da2fb52",
+    "zh:8c25adb15a19da301c478aa1f4a4d8647cabdf8e5dae8331d4490f80ea718c26",
+    "zh:8e129b847401e39fcbc54817726dab877f36b7f00ff5ed76f7b43470abe99ff9",
+    "zh:d268bb267a2d6b39df7ddee8efa7c1ef7a15cf335dfa5f2e64c9dae9b623a1b8",
+    "zh:d6eeb3614a0ab50f8e9ab5666ae5754ea668ce327310e5b21b7f04a18d7611a8",
+    "zh:f5d3c58055dff6e38562b75d3edc908cb2f1e45c6914f6b00f4773359ce49324",
+  ]
+}
+
+provider "registry.terraform.io/hashicorp/null" {
+  version = "3.1.0"
+  hashes = [
+    "h1:vpC6bgUQoJ0znqIKVFevOdq+YQw42bRq0u+H3nto8nA=",
+    "zh:02a1675fd8de126a00460942aaae242e65ca3380b5bb192e8773ef3da9073fd2",
+    "zh:53e30545ff8926a8e30ad30648991ca8b93b6fa496272cd23b26763c8ee84515",
+    "zh:5f9200bf708913621d0f6514179d89700e9aa3097c77dac730e8ba6e5901d521",
+    "zh:9ebf4d9704faba06b3ec7242c773c0fbfe12d62db7d00356d4f55385fc69bfb2",
+    "zh:a6576c81adc70326e4e1c999c04ad9ca37113a6e925aefab4765e5a5198efa7e",
+    "zh:a8a42d13346347aff6c63a37cda9b2c6aa5cc384a55b2fe6d6adfa390e609c53",
+    "zh:c797744d08a5307d50210e0454f91ca4d1c7621c68740441cf4579390452321d",
+    "zh:cecb6a304046df34c11229f20a80b24b1603960b794d68361a67c5efe58e62b8",
+    "zh:e1371aa1e502000d9974cfaff5be4cfa02f47b17400005a16f14d2ef30dc2a70",
+    "zh:fc39cc1fe71234a0b0369d5c5c7f876c71b956d23d7d6f518289737a001ba69b",
+    "zh:fea4227271ebf7d9e2b61b89ce2328c7262acd9fd190e1fd6d15a591abfa848e",
+  ]
+}
+
+provider "registry.terraform.io/hashicorp/tls" {
+  version = "3.1.0"
+  hashes = [
+    "h1:fUJX8Zxx38e2kBln+zWr1Tl41X+OuiE++REjrEyiOM4=",
+    "zh:3d46616b41fea215566f4a957b6d3a1aa43f1f75c26776d72a98bdba79439db6",
+    "zh:623a203817a6dafa86f1b4141b645159e07ec418c82fe40acd4d2a27543cbaa2",
+    "zh:668217e78b210a6572e7b0ecb4134a6781cc4d738f4f5d09eb756085b082592e",
+    "zh:95354df03710691773c8f50a32e31fca25f124b7f3d6078265fdf3c4e1384dca",
+    "zh:9f97ab190380430d57392303e3f36f4f7835c74ea83276baa98d6b9a997c3698",
+    "zh:a16f0bab665f8d933e95ca055b9c8d5707f1a0dd8c8ecca6c13091f40dc1e99d",
+    "zh:be274d5008c24dc0d6540c19e22dbb31ee6bfdd0b2cddd4d97f3cd8a8d657841",
+    "zh:d5faa9dce0a5fc9d26b2463cea5be35f8586ab75030e7fa4d4920cd73ee26989",
+    "zh:e9b672210b7fb410780e7b429975adcc76dd557738ecc7c890ea18942eb321a5",
+    "zh:eb1f8368573d2370605d6dbf60f9aaa5b64e55741d96b5fb026dbfe91de67c0d",
+    "zh:fc1e12b713837b85daf6c3bb703d7795eaf1c5177aebae1afcf811dd7009f4b0",
+  ]
+}
diff --git a/internal/terraform/testdata/with-lock-file/main.tf b/internal/terraform/testdata/with-lock-file/main.tf
new file mode 100644
index 0000000..a05236a
--- /dev/null
+++ b/internal/terraform/testdata/with-lock-file/main.tf
@@ -0,0 +1,33 @@
+/**
+ * Example of 'foo_bar' module in `foo_bar.tf`.
+ *
+ * - list item 1
+ * - list item 2
+ *
+ * Even inline **formatting** in _here_ is possible.
+ * and some [link](https://domain.com/)
+ */
+
+terraform {
+  required_version = ">= 0.12"
+  required_providers {
+    aws = ">= 2.15.0"
+  }
+}
+
+resource "tls_private_key" "baz" {}
+
+data "aws_caller_identity" "current" {
+  provider = "aws"
+}
+
+resource "null_resource" "foo" {}
+
+module "foo" {
+  source  = "bar"
+  version = "1.2.3"
+}
+
+module "foobar" {
+  source  = "git@github.com:module/path?ref=v7.8.9"
+}
diff --git a/internal/terraform/testdata/with-lock-file/outputs.tf b/internal/terraform/testdata/with-lock-file/outputs.tf
new file mode 100644
index 0000000..1024f48
--- /dev/null
+++ b/internal/terraform/testdata/with-lock-file/outputs.tf
@@ -0,0 +1,14 @@
+output C {
+  description = "It's unquoted output."
+  value       = "c"
+}
+
+output "A" {
+  description = "A description"
+  value       = "a"
+}
+
+// B description
+output "B" {
+  value = "b"
+}
diff --git a/internal/terraform/testdata/with-lock-file/variables.tf b/internal/terraform/testdata/with-lock-file/variables.tf
new file mode 100644
index 0000000..c504a0f
--- /dev/null
+++ b/internal/terraform/testdata/with-lock-file/variables.tf
@@ -0,0 +1,30 @@
+// D description
+variable "D" {
+  default = "d"
+}
+
+variable "B" {
+  default = "b"
+}
+
+variable "E" {
+  default = ""
+}
+
+# A Description
+# in multiple lines
+variable A {}
+
+variable "C" {
+  description = "C description"
+  default = "c"
+}
+
+variable "F" {
+  description = "F description"
+}
+
+variable "G" {
+  description = "G description"
+  default     = null
+}