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

fix: handle nil expressions in earlydecoder when parsing outputs with incomplete provider defined functions #324

Merged
merged 2 commits into from
Feb 29, 2024
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
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
156 changes: 156 additions & 0 deletions earlydecoder/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,162 @@ resource "google_something" "test" {
runTestCases(testCases, t, path)
}

func TestLoadModule_nil_expr(t *testing.T) {
path := t.TempDir()

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::
}`,
},
}

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)

files := map[string]*hcl.File{
"test.tf": f,
}

_, diags := LoadModule(path, files)

if len(diags) > 0 {
t.Fatalf("unexpected diagnostics: %s", diags)
}
})
}
}

func TestLoadModule_Variables(t *testing.T) {
path := t.TempDir()

Expand Down
30 changes: 16 additions & 14 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,16 +268,18 @@ 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...)
}
value := cty.NilVal
if attr, defined := content.Attributes["value"]; defined {
// skip if attr.Expr is nil, which can happen when a module is opened that has incomplete
// provider defined functions in outputs (might happen when a wip project is opened)
if attr, defined := content.Attributes["value"]; defined && attr.Expr != nil {
// TODO: Provide context w/ funcs and variables
val, diags := attr.Expr.Value(nil)
if !diags.HasErrors() {
Expand All @@ -300,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