Skip to content

Commit

Permalink
fix: catch and test all possible nil expressions for partial provider…
Browse files Browse the repository at this point in the history
… defined function expressions
  • Loading branch information
ansgarm committed Feb 29, 2024
1 parent aa4f161 commit 2a240b7
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 40 deletions.
4 changes: 2 additions & 2 deletions earlydecoder/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func decodeBackendsBlock(block *hcl.Block) (backend.BackendData, hcl.Diagnostics

switch bType {
case "remote":
if attr, ok := attrs["hostname"]; ok {
if attr, ok := attrs["hostname"]; ok && attr.Expr != nil {
val, vDiags := attr.Expr.Value(nil)
diags = append(diags, vDiags...)
if val.IsWhollyKnown() && val.Type() == cty.String {
Expand All @@ -38,7 +38,7 @@ func decodeCloudBlock(block *hcl.Block) (*backend.Cloud, hcl.Diagnostics) {
// https://developer.hashicorp.com/terraform/language/settings/terraform-cloud#usage-example
// Required for Terraform Enterprise
// Defaults to app.terraform.io for Terraform Cloud
if attr, ok := attrs["hostname"]; ok {
if attr, ok := attrs["hostname"]; ok && attr.Expr != nil {
val, vDiags := attr.Expr.Value(nil)
if val.IsWhollyKnown() && val.Type() == cty.String {
return &backend.Cloud{
Expand Down
170 changes: 145 additions & 25 deletions earlydecoder/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,36 +570,156 @@ resource "google_something" "test" {
func TestLoadModule_nil_expr(t *testing.T) {
path := t.TempDir()

cfg := `
output "foo" {
value = provider::
}`

// We're ignoring diagnostics here, since our config contains invalid HCL
f, _ := hclsyntax.ParseConfig([]byte(cfg), "test.tf", hcl.InitialPos)

files := map[string]*hcl.File{
"test.tf": f,
testCases := []struct {
name string
cfg string
}{
{
"remote backend hostname",
`terraform {
backend "remote" {
hostname = provider::
}
}`,
},
{
"cloud block hostname",
`terraform {
cloud {
hostname = provider::
}
}`,
},
{
"required providers",
`terraform {
required_providers {
aws = provider::
}
}`,
},
{
"required providers nested version",
`terraform {
required_providers {
aws = {
version = provider::
}
}
}`,
},
{
"required providers nested configuration_aliases",
`terraform {
required_providers {
aws = {
source = "hashicorp/aws"
configuration_aliases = [ provider:: ]
}
}
}`,
},
{
"terraform required_version",
`terraform {
required_version = provider::
}`,
},
{
"provider block version",
`provider "aws" {
version = provider::
}`,
},
{
"provider block alias",
`provider "aws" {
alias = provider::
}`,
},
{
"variable description",
`variable "foo" {
description = provider::
}`,
},
{
"variable sensitive",
`variable "foo" {
sensitive = provider::
}`,
},
{
"variable default",
`variable "foo" {
default = provider::
}`,
},
{
"variable type",
`variable "foo" {
type = provider::
}`,
},
{
"output description",
`output "foo" {
description = provider::
}`,
},
{
"output sensitive",
`output "foo" {
sensitive = provider::
}`,
},
{
"output value",
`output "foo" {
value = provider::
}`,
},
{
"module source",
`module "foo" {
source = provider::
}`,
},
{
"module version",
`module "foo" {
version = provider::
}`,
},
{
"resource provider alias",
`resource "aws_instance" "foo" {
provider = provider::
}`,
},
{
"data provider alias",
`data "aws_instance" "foo" {
provider = provider::
}`,
},
}

meta, diags := LoadModule(path, files)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// We're ignoring diagnostics here, since our config contains invalid HCL
f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos)

if len(diags) > 0 {
t.Fatalf("unexpected diagnostics: %s", diags)
}
files := map[string]*hcl.File{
"test.tf": f,
}

expectedMeta := &module.Meta{
Path: path,
ProviderReferences: map[module.ProviderRef]tfaddr.Provider{},
ProviderRequirements: map[tfaddr.Provider]version.Constraints{},
Variables: map[string]module.Variable{},
Outputs: map[string]module.Output{"foo": {}},
Filenames: []string{"test.tf"},
ModuleCalls: map[string]module.DeclaredModuleCall{},
}
_, diags := LoadModule(path, files)

if diff := cmp.Diff(expectedMeta, meta, customComparer...); diff != "" {
t.Fatalf("module meta doesn't match: %s", diff)
if len(diags) > 0 {
t.Fatalf("unexpected diagnostics: %s", diags)
}
})
}
}

Expand Down
26 changes: 13 additions & 13 deletions earlydecoder/load_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {
content, _, contentDiags := block.Body.PartialContent(terraformBlockSchema)
diags = append(diags, contentDiags...)

if attr, defined := content.Attributes["required_version"]; defined {
if attr, defined := content.Attributes["required_version"]; defined && attr.Expr != nil {
var version string
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
diags = append(diags, valDiags...)
Expand Down Expand Up @@ -136,7 +136,7 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {
if _, exists := mod.ProviderRequirements[name]; !exists {
mod.ProviderRequirements[name] = &providerRequirement{}
}
if attr, defined := content.Attributes["version"]; defined {
if attr, defined := content.Attributes["version"]; defined && attr.Expr != nil {
var version string
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
diags = append(diags, valDiags...)
Expand All @@ -147,7 +147,7 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {

providerKey := name
var alias string
if attr, defined := content.Attributes["alias"]; defined {
if attr, defined := content.Attributes["alias"]; defined && attr.Expr != nil {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &alias)
diags = append(diags, valDiags...)
if !valDiags.HasErrors() && alias != "" {
Expand All @@ -171,7 +171,7 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {

mod.DataSources[ds.MapKey()] = ds

if attr, defined := content.Attributes["provider"]; defined {
if attr, defined := content.Attributes["provider"]; defined && attr.Expr != nil {
ref, aDiags := decodeProviderAttribute(attr)
diags = append(diags, aDiags...)
ds.Provider = ref
Expand All @@ -194,7 +194,7 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {

mod.Resources[r.MapKey()] = r

if attr, defined := content.Attributes["provider"]; defined {
if attr, defined := content.Attributes["provider"]; defined && attr.Expr != nil {
ref, aDiags := decodeProviderAttribute(attr)
diags = append(diags, aDiags...)
r.Provider = ref
Expand All @@ -216,22 +216,22 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {
description := ""
isSensitive := false
var valDiags hcl.Diagnostics
if attr, defined := content.Attributes["description"]; defined {
if attr, defined := content.Attributes["description"]; defined && attr.Expr != nil {
valDiags = gohcl.DecodeExpression(attr.Expr, nil, &description)
diags = append(diags, valDiags...)
}
varType := cty.DynamicPseudoType
var defaults *typeexpr.Defaults
if attr, defined := content.Attributes["type"]; defined {
if attr, defined := content.Attributes["type"]; defined && attr.Expr != nil {
varType, defaults, valDiags = typeexpr.TypeConstraintWithDefaults(attr.Expr)
diags = append(diags, valDiags...)
}
if attr, defined := content.Attributes["sensitive"]; defined {
if attr, defined := content.Attributes["sensitive"]; defined && attr.Expr != nil {
valDiags = gohcl.DecodeExpression(attr.Expr, nil, &isSensitive)
diags = append(diags, valDiags...)
}
defaultValue := cty.NilVal
if attr, defined := content.Attributes["default"]; defined {
if attr, defined := content.Attributes["default"]; defined && attr.Expr != nil {
val, diags := attr.Expr.Value(nil)
if !diags.HasErrors() {
if varType != cty.NilType {
Expand Down Expand Up @@ -268,11 +268,11 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {
description := ""
isSensitive := false
var valDiags hcl.Diagnostics
if attr, defined := content.Attributes["description"]; defined {
if attr, defined := content.Attributes["description"]; defined && attr.Expr != nil {
valDiags = gohcl.DecodeExpression(attr.Expr, nil, &description)
diags = append(diags, valDiags...)
}
if attr, defined := content.Attributes["sensitive"]; defined {
if attr, defined := content.Attributes["sensitive"]; defined && attr.Expr != nil {
valDiags = gohcl.DecodeExpression(attr.Expr, nil, &isSensitive)
diags = append(diags, valDiags...)
}
Expand Down Expand Up @@ -302,11 +302,11 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {
var versionCons version.Constraints

var valDiags hcl.Diagnostics
if attr, defined := content.Attributes["source"]; defined {
if attr, defined := content.Attributes["source"]; defined && attr.Expr != nil {
valDiags = gohcl.DecodeExpression(attr.Expr, nil, &source)
diags = append(diags, valDiags...)
}
if attr, defined := content.Attributes["version"]; defined {
if attr, defined := content.Attributes["version"]; defined && attr.Expr != nil {
var versionStr string
valDiags = gohcl.DecodeExpression(attr.Expr, nil, &versionStr)
diags = append(diags, valDiags...)
Expand Down
4 changes: 4 additions & 0 deletions earlydecoder/provider_requirements.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (map[string]*providerRequire
attrs, diags := block.Body.JustAttributes()
reqs := make(map[string]*providerRequirement)
for name, attr := range attrs {
if attr.Expr == nil {
continue
}

// Look for a legacy version in the attribute first
if expr, err := attr.Expr.Value(nil); err == nil && expr.Type().IsPrimitiveType() {
var version string
Expand Down

0 comments on commit 2a240b7

Please sign in to comment.