From 9a9f4e2696e1d2910cf3d87c7565a8cd7a5901c3 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Wed, 24 Jun 2020 10:09:12 -0400 Subject: [PATCH] configs: Fix provider requirements panics When parsing provider requirements we should check the type of the source and version attributes rather than assuming that they are strings. Otherwise an invalid attribute value will cause a panic. --- configs/provider_requirements.go | 71 +++++++++++++++++---------- configs/provider_requirements_test.go | 26 ++++++++++ 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/configs/provider_requirements.go b/configs/provider_requirements.go index 361fec5eb..74188ed81 100644 --- a/configs/provider_requirements.go +++ b/configs/provider_requirements.go @@ -4,6 +4,7 @@ import ( version "github.com/hashicorp/go-version" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/addrs" + "github.com/zclconf/go-cty/cty" ) // RequiredProvider represents a declaration of a dependency on a particular @@ -55,41 +56,61 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Dia vc := VersionConstraint{ DeclRange: attr.Range, } - constraintStr := expr.GetAttr("version").AsString() - constraints, err := version.NewConstraint(constraintStr) - if err != nil { - // NewConstraint doesn't return user-friendly errors, so we'll just - // ignore the provided error and produce our own generic one. + constraint := expr.GetAttr("version") + if !constraint.Type().Equals(cty.String) || constraint.IsNull() { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid version constraint", - Detail: "This string does not use correct version constraint syntax.", + Detail: "Version must be specified as a string.", Subject: attr.Expr.Range().Ptr(), }) } else { - vc.Required = constraints - rp.Requirement = vc + constraintStr := constraint.AsString() + constraints, err := version.NewConstraint(constraintStr) + if err != nil { + // NewConstraint doesn't return user-friendly errors, so we'll just + // ignore the provided error and produce our own generic one. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid version constraint", + Detail: "This string does not use correct version constraint syntax.", + Subject: attr.Expr.Range().Ptr(), + }) + } else { + vc.Required = constraints + rp.Requirement = vc + } } } if expr.Type().HasAttribute("source") { - rp.Source = expr.GetAttr("source").AsString() - - fqn, sourceDiags := addrs.ParseProviderSourceString(rp.Source) - - if sourceDiags.HasErrors() { - hclDiags := sourceDiags.ToHCL() - // The diagnostics from ParseProviderSourceString don't contain - // source location information because it has no context to compute - // them from, and so we'll add those in quickly here before we - // return. - for _, diag := range hclDiags { - if diag.Subject == nil { - diag.Subject = attr.Expr.Range().Ptr() - } - } - diags = append(diags, hclDiags...) + source := expr.GetAttr("source") + if !source.Type().Equals(cty.String) || source.IsNull() { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid source", + Detail: "Source must be specified as a string.", + Subject: attr.Expr.Range().Ptr(), + }) } else { - rp.Type = fqn + rp.Source = source.AsString() + + fqn, sourceDiags := addrs.ParseProviderSourceString(rp.Source) + + if sourceDiags.HasErrors() { + hclDiags := sourceDiags.ToHCL() + // The diagnostics from ParseProviderSourceString don't contain + // source location information because it has no context to compute + // them from, and so we'll add those in quickly here before we + // return. + for _, diag := range hclDiags { + if diag.Subject == nil { + diag.Subject = attr.Expr.Range().Ptr() + } + } + diags = append(diags, hclDiags...) + } else { + rp.Type = fqn + } } } diff --git a/configs/provider_requirements_test.go b/configs/provider_requirements_test.go index 3d1da1a23..42e4bfbe1 100644 --- a/configs/provider_requirements_test.go +++ b/configs/provider_requirements_test.go @@ -306,6 +306,32 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) { }, Error: "Invalid required_providers syntax", }, + "invalid source attribute type": { + Block: &hcl.Block{ + Type: "required_providers", + Body: hcltest.MockBody(&hcl.BodyContent{ + Attributes: hcl.Attributes{ + "my-test": { + Name: "my-test", + Expr: hcltest.MockExprLiteral(cty.ObjectVal(map[string]cty.Value{ + "source": cty.DynamicVal, + })), + }, + }, + }), + DefRange: blockRange, + }, + Want: &RequiredProviders{ + RequiredProviders: map[string]*RequiredProvider{ + "my-test": { + Name: "my-test", + DeclRange: mockRange, + }, + }, + DeclRange: blockRange, + }, + Error: "Invalid source", + }, } for name, test := range tests {