From aeccfcd1dc8167ccad47addc7ce853d41527255c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 27 Sep 2019 09:40:00 -0700 Subject: [PATCH] projects/projectconfigs: Decode "context" blocks --- projects/projectconfigs/context.go | 50 +++++++++++++++++ .../projectconfigs/projectconfigs_test.go | 54 +++++++++++++++++++ .../testdata/context/.terraform-project.hcl | 5 ++ 3 files changed, 109 insertions(+) create mode 100644 projects/projectconfigs/testdata/context/.terraform-project.hcl diff --git a/projects/projectconfigs/context.go b/projects/projectconfigs/context.go index d242ad6178..5d9d5af8af 100644 --- a/projects/projectconfigs/context.go +++ b/projects/projectconfigs/context.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/hcl2/hcl" + "github.com/hashicorp/hcl2/ext/typeexpr" "github.com/hashicorp/hcl2/hcl/hclsyntax" "github.com/zclconf/go-cty/cty" @@ -34,6 +35,10 @@ type ContextValue struct { // this is a required context value. Default hcl.Expression + // Description is a human-oriented description of the purpose of this + // context value, written in full sentences in a natural language. + Description string + // DeclRange is the source range of the block header of this block, // for use in diagnostic messages. NameRange is the range of the // Name string specifically. @@ -57,5 +62,50 @@ func decodeContextBlock(block *hcl.Block) (*ContextValue, tfdiags.Diagnostics) { }) } + content, hclDiags := block.Body.Content(contextSchema) + diags = diags.Append(hclDiags) + + if attr, ok := content.Attributes["type"]; ok { + ty, hclDiags := typeexpr.TypeConstraint(attr.Expr) + diags = diags.Append(hclDiags) + cv.Type = ty + } else { + cv.Type = cty.DynamicPseudoType + } + + if attr, ok := content.Attributes["default"]; ok { + cv.Default = attr.Expr + } + + if attr, ok := content.Attributes["description"]; ok { + // We don't allow variables/functions in the description because + // we want to be able to display it in a UI that might be prompting + // for data that would be needed to evaluate expressions, so we'll + // just evaluate this right now and HCL will generate "variables cannot + // be used here" errors in case a user tries. + val, hclDiags := attr.Expr.Value(nil) + diags = diags.Append(hclDiags) + if !diags.HasErrors() { + if val.Type().Equals(cty.String) && !val.IsNull() { + cv.Description = val.AsString() + } else { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid description for context value", + Detail: "The description of a context value must be a string and should contain a natural language description of the meaning of this context value using full sentences.", + Subject: attr.Expr.StartRange().Ptr(), + }) + } + } + } + return cv, diags } + +var contextSchema = &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + {Name: "type"}, + {Name: "default"}, + {Name: "description"}, + }, +} diff --git a/projects/projectconfigs/projectconfigs_test.go b/projects/projectconfigs/projectconfigs_test.go index a966c998b5..ce7c7441a8 100644 --- a/projects/projectconfigs/projectconfigs_test.go +++ b/projects/projectconfigs/projectconfigs_test.go @@ -5,6 +5,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl2/hcl" + "github.com/hashicorp/hcl2/hcl/hclsyntax" + "github.com/hashicorp/terraform/tfdiags" + "github.com/zclconf/go-cty/cty" ) func TestLoad(t *testing.T) { @@ -26,6 +30,56 @@ func TestLoad(t *testing.T) { t.Errorf("unexpected result\n%s", diff) } }) + t.Run("context", func(t *testing.T) { + cfg, diags := Load("testdata/context") + if diags.HasErrors() { + t.Fatalf("Unexpected problems: %s", diags.Err().Error()) + } + + got := cfg.Context + want := map[string]*ContextValue{ + "foo": { + Name: "foo", + Type: cty.String, + Description: "The foo thing.", + Default: &hclsyntax.TemplateExpr{ + Parts: []hclsyntax.Expression{ + &hclsyntax.LiteralValueExpr{ + Val: cty.StringVal("bar"), + SrcRange: hcl.Range{ + Filename: "testdata/context/.terraform-project.hcl", + Start: hcl.Pos{Line: 3, Column: 18, Byte: 56}, + End: hcl.Pos{Line: 3, Column: 21, Byte: 59}, + }, + }, + }, + SrcRange: hcl.Range{ + Filename: "testdata/context/.terraform-project.hcl", + Start: hcl.Pos{Line: 3, Column: 17, Byte: 55}, + End: hcl.Pos{Line: 3, Column: 22, Byte: 60}, + }, + }, + DeclRange: tfdiags.SourceRange{ + Filename: "testdata/context/.terraform-project.hcl", + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 1, Column: 14, Byte: 13}, + }, + NameRange: tfdiags.SourceRange{ + Filename: "testdata/context/.terraform-project.hcl", + Start: tfdiags.SourcePos{Line: 1, Column: 9, Byte: 8}, + End: tfdiags.SourcePos{Line: 1, Column: 14, Byte: 13}, + }, + }, + } + diff := cmp.Diff( + want, got, + cmp.Comparer(cty.Type.Equals), + cmp.Comparer(cty.Value.RawEquals), + ) + if diff != "" { + t.Errorf("unexpected result\n%s", diff) + } + }) } func TestFindProjectRoot(t *testing.T) { diff --git a/projects/projectconfigs/testdata/context/.terraform-project.hcl b/projects/projectconfigs/testdata/context/.terraform-project.hcl new file mode 100644 index 0000000000..7eec45a7eb --- /dev/null +++ b/projects/projectconfigs/testdata/context/.terraform-project.hcl @@ -0,0 +1,5 @@ +context "foo" { + type = string + default = "bar" + description = "The foo thing." +}