From e01d37d0dc7f53960d3dc3d3ce9e070c569b29bb Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 11 Jan 2021 18:11:37 -0500 Subject: [PATCH] Block.AttribuuteByPath There are a few places where we want to perform some transformation on a cty.Value, but require information from the schema. Rather than create bespoke functions to walk the cty.Value and schema in concert, we can provide Attribute information from a cty.Path allowing the use of Value.Transform in these cases. --- configs/configschema/path.go | 29 +++++++ configs/configschema/path_test.go | 121 ++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 configs/configschema/path.go create mode 100644 configs/configschema/path_test.go diff --git a/configs/configschema/path.go b/configs/configschema/path.go new file mode 100644 index 000000000..4c48c1a04 --- /dev/null +++ b/configs/configschema/path.go @@ -0,0 +1,29 @@ +package configschema + +import ( + "github.com/zclconf/go-cty/cty" +) + +// AttributeByPath looks up the Attribute schema which corresponds to the given +// cty.Path. A nil value is returned if the given path does not correspond to a +// specific attribute. +// TODO: this will need to be updated for nested attributes +func (b *Block) AttributeByPath(path cty.Path) *Attribute { + block := b + for _, step := range path { + switch step := step.(type) { + case cty.GetAttrStep: + if attr := block.Attributes[step.Name]; attr != nil { + return attr + } + + if nestedBlock := block.BlockTypes[step.Name]; nestedBlock != nil { + block = &nestedBlock.Block + continue + } + + return nil + } + } + return nil +} diff --git a/configs/configschema/path_test.go b/configs/configschema/path_test.go new file mode 100644 index 000000000..c4f673bad --- /dev/null +++ b/configs/configschema/path_test.go @@ -0,0 +1,121 @@ +package configschema + +import ( + "testing" + + "github.com/zclconf/go-cty/cty" +) + +func TestAttributeByPath(t *testing.T) { + schema := &Block{ + Attributes: map[string]*Attribute{ + "a1": {Description: "a1"}, + "a2": {Description: "a2"}, + }, + BlockTypes: map[string]*NestedBlock{ + "b1": { + Nesting: NestingList, + Block: Block{ + Attributes: map[string]*Attribute{ + "a3": {Description: "a3"}, + "a4": {Description: "a4"}, + }, + BlockTypes: map[string]*NestedBlock{ + "b2": { + Nesting: NestingMap, + Block: Block{ + Attributes: map[string]*Attribute{ + "a5": {Description: "a5"}, + "a6": {Description: "a6"}, + }, + }, + }, + }, + }, + }, + "b3": { + Nesting: NestingMap, + Block: Block{ + Attributes: map[string]*Attribute{ + "a7": {Description: "a7"}, + "a8": {Description: "a8"}, + }, + BlockTypes: map[string]*NestedBlock{ + "b4": { + Nesting: NestingSet, + Block: Block{ + Attributes: map[string]*Attribute{ + "a9": {Description: "a9"}, + "a10": {Description: "a10"}, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range []struct { + path cty.Path + attrDescription string + exists bool + }{ + { + cty.GetAttrPath("a2"), + "a2", + true, + }, + { + cty.GetAttrPath("b1"), + "block", + false, + }, + { + cty.GetAttrPath("b1").IndexInt(1).GetAttr("a3"), + "a3", + true, + }, + { + cty.GetAttrPath("b1").IndexInt(1).GetAttr("b2").IndexString("foo").GetAttr("a7"), + "missing", + false, + }, + { + cty.GetAttrPath("b1").IndexInt(1).GetAttr("b2").IndexString("foo").GetAttr("a6"), + "a6", + true, + }, + { + cty.GetAttrPath("b3").IndexString("foo").GetAttr("b2").IndexString("foo").GetAttr("a7"), + "missing_block", + false, + }, + { + cty.GetAttrPath("b3").IndexString("foo").GetAttr("a7"), + "a7", + true, + }, + { + // Index steps don't apply to the schema, so the set Index value doesn't matter. + cty.GetAttrPath("b3").IndexString("foo").GetAttr("b4").Index(cty.EmptyObjectVal).GetAttr("a9"), + "a9", + true, + }, + } { + t.Run(tc.attrDescription, func(t *testing.T) { + attr := schema.AttributeByPath(tc.path) + if !tc.exists && attr == nil { + return + } + + if attr == nil { + t.Fatalf("missing attribute from path %#v\n", tc.path) + } + + if attr.Description != tc.attrDescription { + t.Fatalf("expected Attribute for %q, got %#v\n", tc.attrDescription, attr) + } + }) + } +}