Skip to content
This repository was archived by the owner on Nov 20, 2025. It is now read-only.

Commit 205e2b5

Browse files
bugfix: provider version coming as 0.0.0 or empty (#1553)
1 parent 4f1e403 commit 205e2b5

File tree

13 files changed

+465
-106
lines changed

13 files changed

+465
-106
lines changed

pkg/iac-providers/terraform/commons/load-dir.go

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ func (t TerraformDirectoryLoader) loadDirRecursive(dirList []string) (output.All
158158
t.addError(errMessage, dir)
159159
}
160160

161-
// getting provider version for the root module
162-
providerVersion := GetModuleProviderVersion(rootMod)
163-
164161
// get unified config for the current directory
165162
unified, diags := t.buildUnifiedConfig(rootMod, dir)
166163
// Get the downloader chache
@@ -209,12 +206,7 @@ func (t TerraformDirectoryLoader) loadDirRecursive(dirList []string) (output.All
209206
}
210207

211208
resourceConfig.TerraformVersion = t.terraformVersion
212-
resourceConfig.ProviderVersion = providerVersion
213-
214-
// if root module do not have provider contraints fetch the latest compatible version
215-
if resourceConfig.ProviderVersion == "" {
216-
resourceConfig.ProviderVersion = LatestProviderVersion(managedResource.Provider, t.terraformVersion)
217-
}
209+
resourceConfig.ProviderVersion = GetModuleProviderVersion(current.Config.Module, managedResource.Provider, t.terraformVersion)
218210
// set module name
219211
resourceConfig.ModuleName = current.Name
220212

@@ -295,9 +287,6 @@ func (t TerraformDirectoryLoader) loadDirNonRecursive() (output.AllResourceConfi
295287
t.addError(errMessage, t.absRootDir)
296288
}
297289

298-
// getting provider version for the root module
299-
providerVersion := GetModuleProviderVersion(rootMod)
300-
301290
// get unified config for the current directory
302291
unified, diags := t.buildUnifiedConfig(rootMod, t.absRootDir)
303292

@@ -360,12 +349,7 @@ func (t TerraformDirectoryLoader) loadDirNonRecursive() (output.AllResourceConfi
360349
}
361350

362351
resourceConfig.TerraformVersion = t.terraformVersion
363-
resourceConfig.ProviderVersion = providerVersion
364-
365-
// if root module do not have provider contraints fetch the latest compatible version
366-
if resourceConfig.ProviderVersion == "" {
367-
resourceConfig.ProviderVersion = LatestProviderVersion(managedResource.Provider, t.terraformVersion)
368-
}
352+
resourceConfig.ProviderVersion = GetModuleProviderVersion(current.Config.Module, managedResource.Provider, t.terraformVersion)
369353

370354
if isRemoteModule {
371355
resourceConfig.IsRemoteModule = &isRemoteModule

pkg/iac-providers/terraform/commons/load-file.go

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,11 @@ func LoadIacFile(absFilePath, terraformVersion string) (allResourcesConfig outpu
4242
return allResourcesConfig, fmt.Errorf(errMessage)
4343
}
4444

45-
if diags != nil {
45+
if diags.HasErrors() {
4646
errMessage := fmt.Sprintf("failed to load iac file '%s'. error:\n%v\n", absFilePath, getErrorMessagesFromDiagnostics(diags))
4747
zap.S().Debug(errMessage)
4848
return allResourcesConfig, fmt.Errorf(errMessage)
4949
}
50-
// getting provider version for the file
51-
providerVersion := GetFileProviderVersion(hclFile)
5250

5351
// initialize normalized output
5452
allResourcesConfig = make(map[string][]output.ResourceConfig)
@@ -62,21 +60,16 @@ func LoadIacFile(absFilePath, terraformVersion string) (allResourcesConfig outpu
6260
return allResourcesConfig, fmt.Errorf("failed to create ResourceConfig")
6361
}
6462

63+
resourceConfig.TerraformVersion = terraformVersion
64+
managedResource.Provider = ResolveProvider(managedResource, hclFile.RequiredProviders)
65+
resourceConfig.ProviderVersion = GetProviderVersion(hclFile, managedResource.Provider, terraformVersion)
6566
// set module name
6667
// module name for the file scan will always be root
6768
resourceConfig.ModuleName = "root"
6869

6970
// extract file name from path
7071
resourceConfig.Source = getFileName(resourceConfig.Source)
7172

72-
resourceConfig.TerraformVersion = terraformVersion
73-
resourceConfig.ProviderVersion = providerVersion
74-
75-
// if root module do not have provider contraints fetch the latest compatible version
76-
if resourceConfig.ProviderVersion == "" {
77-
resourceConfig.ProviderVersion = LatestProviderVersion(managedResource.Provider, terraformVersion)
78-
}
79-
8073
// append to normalized output
8174
if _, present := allResourcesConfig[resourceConfig.Type]; !present {
8275
allResourcesConfig[resourceConfig.Type] = []output.ResourceConfig{resourceConfig}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
Copyright (C) 2022 Tenable, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package commons
18+
19+
import (
20+
"encoding/json"
21+
"os"
22+
"path/filepath"
23+
"testing"
24+
25+
"github.com/tenable/terrascan/pkg/iac-providers/output"
26+
"github.com/tenable/terrascan/pkg/iac-providers/terraform/commons/test"
27+
"github.com/tenable/terrascan/pkg/utils"
28+
)
29+
30+
func TestLoadIacFile(t *testing.T) {
31+
type args struct {
32+
absFilePath string
33+
terraformVersion string
34+
}
35+
tests := []struct {
36+
name string
37+
args args
38+
outputJSON string
39+
wantAllResourcesConfig output.AllResourceConfigs
40+
wantErr bool
41+
}{
42+
{
43+
name: "file with no provider defined",
44+
args: args{
45+
absFilePath: filepath.Join(testDataDir, "terraform_iac_files", "with_no_provider.tf"),
46+
terraformVersion: "0.15.0",
47+
},
48+
outputJSON: filepath.Join(testDataDir, "tfjson", "output_no_provider_defined.json"),
49+
},
50+
{
51+
name: "file with provider config",
52+
args: args{
53+
absFilePath: filepath.Join(testDataDir, "terraform_iac_files", "with_provider_config.tf"),
54+
terraformVersion: "0.15.0",
55+
},
56+
outputJSON: filepath.Join(testDataDir, "tfjson", "output_with_provider_config.json"),
57+
},
58+
{
59+
name: "file with required provider",
60+
args: args{
61+
absFilePath: filepath.Join(testDataDir, "terraform_iac_files", "with_required_provider.tf"),
62+
terraformVersion: "0.15.0",
63+
},
64+
outputJSON: filepath.Join(testDataDir, "tfjson", "output_with_required_provider.json"),
65+
},
66+
}
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
got, err := LoadIacFile(tt.args.absFilePath, tt.args.terraformVersion)
70+
if (err != nil) != tt.wantErr {
71+
t.Errorf("LoadIacFile() error = %v, wantErr %v", err, tt.wantErr)
72+
return
73+
}
74+
// if !reflect.DeepEqual(gotAllResourcesConfig, tt.wantAllResourcesConfig) {
75+
// t.Errorf("LoadIacFile() = %v, want %v", gotAllResourcesConfig, tt.wantAllResourcesConfig)
76+
// }
77+
78+
var want output.AllResourceConfigs
79+
80+
// Read the expected value and unmarshal into want
81+
contents, _ := os.ReadFile(tt.outputJSON)
82+
if utils.IsWindowsPlatform() {
83+
contents = utils.ReplaceWinNewLineBytes(contents)
84+
}
85+
86+
err = json.Unmarshal(contents, &want)
87+
if err != nil {
88+
t.Errorf("unexpected error unmarshalling want: %v", err)
89+
}
90+
91+
match, err := test.IdenticalAllResourceConfigs(got, want)
92+
if err != nil {
93+
t.Errorf("unexpected error checking result: %v", err)
94+
}
95+
if !match {
96+
g, _ := json.MarshalIndent(got, "", " ")
97+
w, _ := json.MarshalIndent(want, "", " ")
98+
t.Errorf("got '%v', want: '%v'", string(g), string(w))
99+
}
100+
})
101+
}
102+
}

pkg/iac-providers/terraform/commons/terraform-provider.go

Lines changed: 76 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import (
1010
"strings"
1111

1212
"github.com/apparentlymart/go-versions/versions"
13-
"github.com/apparentlymart/go-versions/versions/constraints"
1413
"github.com/hashicorp/terraform/addrs"
15-
"github.com/hashicorp/terraform/configs"
14+
hclConfigs "github.com/hashicorp/terraform/configs"
1615
httputils "github.com/tenable/terrascan/pkg/utils/http"
1716
"go.uber.org/zap"
1817
)
@@ -22,11 +21,7 @@ const (
2221
terraformVersionHeader = "X-Terraform-Version"
2322
)
2423

25-
// VersionConstraints ...
26-
type VersionConstraints = constraints.IntersectionSpec
27-
28-
// Requirements ...
29-
type Requirements map[addrs.Provider]VersionConstraints
24+
var versionCache = make(map[string]string)
3025

3126
// ProviderVersions ...
3227
type ProviderVersions struct {
@@ -37,14 +32,10 @@ type ProviderVersions struct {
3732
Warnings []string `json:"warnings"`
3833
}
3934

40-
// ProviderVersionList fetches all the versions of terraform providers
41-
func ProviderVersionList(ctx context.Context, addr addrs.Provider, terraformVersion string) (versions.List, []string, error) {
35+
// providerVersionList fetches all the versions of terraform providers
36+
func providerVersionList(ctx context.Context, addr addrs.Provider, terraformVersion string) (versions.List, []string, error) {
4237
zap.S().Debugf("fetching list of providers metadata, hostname: %q, type: %q, namespace: %q, ", addr.Hostname.String(), addr.Namespace, addr.Type)
4338

44-
if addr.Hostname.String() == "" {
45-
return nil, nil, fmt.Errorf("error preparing the providers list endpoint, error: hostname can't be empty")
46-
}
47-
4839
endpointURL, err := url.Parse(path.Join(apiVersion, "providers", addr.Namespace, addr.Type, "versions"))
4940
if err != nil {
5041
return nil, nil, fmt.Errorf("error preparing the providers list endpoint, error: %s", err.Error())
@@ -92,76 +83,108 @@ func ProviderVersionList(ctx context.Context, addr addrs.Provider, terraformVers
9283
}
9384
versionList = append(versionList, ver)
9485
}
95-
// versionList.Newest()
96-
9786
return versionList, body.Warnings, nil
9887
}
9988

100-
var versionCache = make(map[string]string)
101-
102-
// LatestProviderVersion returns the latest published version for the asked provider.
89+
// latestProviderVersion returns the latest published version for the asked provider.
10390
// It returns "0.0.0" in case its not available
104-
func LatestProviderVersion(addr addrs.Provider, terraformVersion string) string {
91+
func latestProviderVersion(addr addrs.Provider, terraformVersion string) string {
10592

10693
// check if the cache has the version info
10794
if v, found := versionCache[fmt.Sprintf("%s-%s", addr.Type, terraformVersion)]; found {
10895
return v
10996
}
110-
versionList, _, err := ProviderVersionList(context.TODO(), addr, terraformVersion)
97+
versionList, _, err := providerVersionList(context.TODO(), addr, terraformVersion)
11198
if err != nil {
112-
zap.S().Errorf("failed to fetch latest version for terraform provider, error: %s", err.Error())
99+
zap.S().Warnf("failed to fetch latest version for terraform provider, error: %s", err.Error())
100+
return versionList.Newest().String()
113101
}
114102
// update cache
115103
versionCache[fmt.Sprintf("%s-%s", addr.Type, terraformVersion)] = versionList.Newest().String()
116104
return versionList.Newest().String()
117105
}
118106

119-
// ParseVersionConstraints parses a "Ruby-like" version constraint string
120-
// into a VersionConstraints value.
121-
func ParseVersionConstraints(str string) (VersionConstraints, error) {
122-
return constraints.ParseRubyStyleMulti(str)
107+
// GetProviderVersion identifies the version constraints for file resources.
108+
func GetProviderVersion(f *hclConfigs.File, addr addrs.Provider, terraformVersion string) string {
109+
version := ""
110+
111+
for _, rps := range f.RequiredProviders {
112+
if rp, exist := rps.RequiredProviders[addr.Type]; exist {
113+
version = trimVersionConstraints(rp.Requirement.Required.String())
114+
break
115+
}
116+
}
117+
118+
// older version of terraform (terraform version < 1.x) may have version in provider block
119+
if len(version) == 0 {
120+
for _, pc := range f.ProviderConfigs {
121+
if pc.Name == addr.Type {
122+
version = trimVersionConstraints(pc.Version.Required.String())
123+
break
124+
}
125+
}
126+
}
127+
128+
// fetch latest version
129+
if len(version) == 0 {
130+
version = latestProviderVersion(addr, terraformVersion)
131+
}
132+
v, err := versions.ParseVersion(version)
133+
if err != nil {
134+
zap.S().Warnf("failed to parse provider version: %s", err.Error())
135+
return ""
136+
}
137+
return v.String()
123138
}
124139

125-
// GetModuleProviderVersion gets the provider version form the 'required_providers' block for module.
126-
// if the 'required_providers' is not defined, it returns empty string
127-
func GetModuleProviderVersion(m *configs.Module) string {
140+
// GetModuleProviderVersion identifies the version constraints for module resources.
141+
func GetModuleProviderVersion(module *hclConfigs.Module, addr addrs.Provider, terraformVersion string) string {
128142
version := ""
129-
if m == nil || m.ProviderRequirements == nil {
130-
return version
143+
144+
if rp, exist := module.ProviderRequirements.RequiredProviders[addr.Type]; exist {
145+
version = trimVersionConstraints(rp.Requirement.Required.String())
131146
}
132-
for _, requiredProvider := range m.ProviderRequirements.RequiredProviders {
133-
if requiredProvider != nil && len(requiredProvider.Requirement.Required) > 0 {
134-
version = requiredProvider.Requirement.Required[0].String()
147+
148+
// older version of terraform (terraform version < 1.x) may have version in provider block
149+
if len(version) == 0 {
150+
if pc, exist := module.ProviderConfigs[addr.Type]; exist {
151+
version = trimVersionConstraints(pc.Version.Required.String())
135152
}
136153
}
137154

138-
// trim version string
139-
s := strings.Split(version, " ")
140-
if len(s) > 1 {
141-
version = s[1]
155+
// fetch latest version
156+
if len(version) == 0 {
157+
version = latestProviderVersion(addr, terraformVersion)
142158
}
143-
144-
return version
159+
v, err := versions.ParseVersion(version)
160+
if err != nil {
161+
zap.S().Warnf("failed to parse provider version: %s", err.Error())
162+
return ""
163+
}
164+
return v.String()
145165
}
146166

147-
// GetFileProviderVersion gets the provider version form the 'required_providers' block for the file.
148-
// if the 'required_providers' is not defined, it returns empty string
149-
func GetFileProviderVersion(f *configs.File) string {
150-
version := ""
151-
if f == nil || f.RequiredProviders == nil || len(f.RequiredProviders) == 0 {
152-
return version
167+
// ResolveProvider resolves provider addr
168+
func ResolveProvider(resource *hclConfigs.Resource, requiredProviders []*hclConfigs.RequiredProviders) addrs.Provider {
169+
implied, err := addrs.ParseProviderPart(resource.Addr().ImpliedProvider())
170+
if err != nil {
171+
zap.S().Warnf("failed to parse provider namespace or type: %s", err.Error())
172+
return addrs.NewDefaultProvider("aws")
153173
}
154-
for _, requiredProvider := range f.RequiredProviders[0].RequiredProviders {
155-
if requiredProvider != nil && len(requiredProvider.Requirement.Required) > 0 {
156-
version = requiredProvider.Requirement.Required[0].String()
174+
for _, rp := range requiredProviders {
175+
if provider, exists := rp.RequiredProviders[implied]; exists {
176+
return provider.Type
157177
}
158178
}
179+
return addrs.ImpliedProviderForUnqualifiedType(implied)
180+
}
159181

160-
// trim version string
161-
s := strings.Split(version, " ")
182+
// trimVersionConstraints trim version constraints from string.
183+
// e.g. "~> 3.0.2" will become "3.0.2"
184+
func trimVersionConstraints(v string) string {
185+
s := strings.Split(v, " ")
162186
if len(s) > 1 {
163-
version = s[1]
187+
v = s[1]
164188
}
165-
166-
return version
189+
return v
167190
}

0 commit comments

Comments
 (0)