Skip to content

Commit

Permalink
decoder: Ensure partially unknown dependent body is handled (#339)
Browse files Browse the repository at this point in the history
* decoder: Ensure partially unknown dependent body is handled

* decoder: Report merging result as enum instead of boolean
  • Loading branch information
radeksimko authored Nov 3, 2023
1 parent caa96d6 commit fc59723
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 42 deletions.
4 changes: 2 additions & 2 deletions decoder/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ func (d *PathDecoder) hoverContentForLabel(i int, block *hclsyntax.Block, bSchem
labelSchema := bSchema.Labels[i]

if labelSchema.IsDepKey {
bs, _, ok := schemahelper.NewBlockSchema(bSchema).DependentBodySchema(block.AsHCLBlock())
if ok {
bs, _, result := schemahelper.NewBlockSchema(bSchema).DependentBodySchema(block.AsHCLBlock())
if result == schemahelper.LookupSuccessful || result == schemahelper.LookupPartiallySuccessful {
content := fmt.Sprintf("`%s`", value)
if bs.Detail != "" {
content += " " + bs.Detail
Expand Down
15 changes: 5 additions & 10 deletions decoder/internal/schemahelper/block_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/hashicorp/hcl/v2"
)

func MergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (*schema.BodySchema, bool) {
func MergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (*schema.BodySchema, LookupResult) {
mergedSchema := &schema.BodySchema{}
if blockSchema.Body != nil {
mergedSchema = blockSchema.Body.Copy()
Expand All @@ -26,8 +26,8 @@ func MergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (*
mergedSchema.ImpliedOrigins = make([]schema.ImpliedOrigin, 0)
}

depSchema, depKeys, ok := NewBlockSchema(blockSchema).DependentBodySchema(block)
if ok {
depSchema, _, result := NewBlockSchema(blockSchema).DependentBodySchema(block)
if result == LookupSuccessful || result == LookupPartiallySuccessful {
for name, attr := range depSchema.Attributes {
if _, exists := mergedSchema.Attributes[name]; !exists {
mergedSchema.Attributes[name] = attr
Expand Down Expand Up @@ -71,7 +71,7 @@ func MergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (*
if depSchema.Extensions != nil {
mergedSchema.Extensions = depSchema.Extensions.Copy()
}
} else if !ok && mergedSchema.Extensions != nil && mergedSchema.Extensions.DynamicBlocks && len(mergedSchema.Blocks) > 0 {
} else if (result == LookupFailed || result == NoDependentKeys) && mergedSchema.Extensions != nil && mergedSchema.Extensions.DynamicBlocks && len(mergedSchema.Blocks) > 0 {
// dynamic blocks are only relevant for dependent schemas,
// but we may end up here because the schema is a result
// of merged static + dependent schema from previous iteration
Expand All @@ -90,10 +90,5 @@ func MergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (*
mergedSchema.Blocks["dynamic"] = buildDynamicBlockSchema(mergedSchema)
}

expectedDepBody := len(depKeys.Labels) > 0 || len(depKeys.Attributes) > 0

// report success either if there wasn't any dependent body merging to do
// or if the merging was successful

return mergedSchema, !expectedDepBody || ok
return mergedSchema, result
}
30 changes: 25 additions & 5 deletions decoder/internal/schemahelper/dependent_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import (
"github.com/zclconf/go-cty/cty"
)

type LookupResult int

const (
LookupFailed LookupResult = iota
LookupSuccessful
LookupPartiallySuccessful
NoDependentKeys
)

type blockSchema struct {
*schema.BlockSchema
seenNestedDepKeys bool
Expand All @@ -23,16 +32,23 @@ func NewBlockSchema(bs *schema.BlockSchema) blockSchema {

// DependentBodySchema finds relevant BodySchema based on dependency keys
// such as a label or an attribute (or combination of both).
func (bs blockSchema) DependentBodySchema(block *hcl.Block) (*schema.BodySchema, schema.DependencyKeys, bool) {
func (bs blockSchema) DependentBodySchema(block *hcl.Block) (*schema.BodySchema, schema.DependencyKeys, LookupResult) {
result := LookupFailed

dks := dependencyKeysFromBlock(block, bs)
b, err := dks.MarshalJSON()
if err != nil {
return nil, schema.DependencyKeys{}, false
return nil, schema.DependencyKeys{}, result
}

if len(dks.Labels) == 0 && len(dks.Attributes) == 0 {
return bs.Body, schema.DependencyKeys{}, NoDependentKeys
}

key := schema.SchemaKey(string(b))
depBodySchema, ok := bs.DependentBody[key]
if ok {
result = LookupSuccessful
hasDepKeys := false
for _, attr := range depBodySchema.Attributes {
if attr.IsDepKey {
Expand All @@ -44,13 +60,17 @@ func (bs blockSchema) DependentBodySchema(block *hcl.Block) (*schema.BodySchema,
mergedBlockSchema := NewBlockSchema(bs.Copy())
mergedBlockSchema.seenNestedDepKeys = true
mergedBlockSchema.Body = depBodySchema
if depBodySchema, dks, ok := mergedBlockSchema.DependentBodySchema(block); ok {
return depBodySchema, dks, ok
if depBodySchema, dks, nestedOk := mergedBlockSchema.DependentBodySchema(block); nestedOk == LookupSuccessful {
return depBodySchema, dks, LookupSuccessful
} else {
// Ensure we report lookup failure overall if we couldn't
// lookup nested dependent body
result = LookupPartiallySuccessful
}
}
}

return depBodySchema, dks, ok
return depBodySchema, dks, result
}

func dependencyKeysFromBlock(block *hcl.Block, blockSchema blockSchema) schema.DependencyKeys {
Expand Down
111 changes: 92 additions & 19 deletions decoder/internal/schemahelper/dependent_body_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ func TestBodySchema_DependentBodySchema_label_basic(t *testing.T) {
},
}

bodySchema, _, ok := NewBlockSchema(bSchema).DependentBodySchema(block)
if !ok {
bodySchema, _, result := NewBlockSchema(bSchema).DependentBodySchema(block)
if result != LookupSuccessful {
t.Fatal("expected to find body schema for 'theircloud' label")
}
expectedSchema := &schema.BodySchema{
Expand Down Expand Up @@ -104,8 +104,8 @@ func TestBodySchema_DependentBodySchema_mismatchingLabels(t *testing.T) {
},
}

_, _, ok := NewBlockSchema(bSchema).DependentBodySchema(block)
if ok {
_, _, result := NewBlockSchema(bSchema).DependentBodySchema(block)
if result != LookupFailed {
t.Fatal("expected to not find body schema for mismatching label schema")
}
}
Expand Down Expand Up @@ -170,17 +170,17 @@ func TestBodySchema_DependentBodySchema_dependentAttr(t *testing.T) {
},
}

bodySchema, _, ok := NewBlockSchema(bSchema).DependentBodySchema(&hcl.Block{
bodySchema, _, result := NewBlockSchema(bSchema).DependentBodySchema(&hcl.Block{
Labels: []string{"remote_state"},
})
if !ok {
if result != LookupSuccessful {
t.Fatal("expected to find body schema for nested dependent schema")
}
if diff := cmp.Diff(firstDepBody, bodySchema, ctydebug.CmpOptions); diff != "" {
t.Fatalf("mismatching body schema: %s", diff)
}

bodySchema, _, ok = NewBlockSchema(bSchema).DependentBodySchema(&hcl.Block{
bodySchema, _, result = NewBlockSchema(bSchema).DependentBodySchema(&hcl.Block{
Labels: []string{"remote_state"},
Body: &hclsyntax.Body{
Attributes: hclsyntax.Attributes{
Expand All @@ -193,7 +193,7 @@ func TestBodySchema_DependentBodySchema_dependentAttr(t *testing.T) {
},
},
})
if !ok {
if result != LookupSuccessful {
t.Fatal("expected to find body schema for nested dependent schema")
}
if diff := cmp.Diff(secondDepBody, bodySchema, ctydebug.CmpOptions); diff != "" {
Expand Down Expand Up @@ -256,7 +256,7 @@ func TestBodySchema_DependentBodySchema_missingDependentAttr(t *testing.T) {
},
}

bodySchema, _, ok := NewBlockSchema(bSchema).DependentBodySchema(&hcl.Block{
bodySchema, _, result := NewBlockSchema(bSchema).DependentBodySchema(&hcl.Block{
Labels: []string{"remote_state"},
Body: &hclsyntax.Body{
Attributes: hclsyntax.Attributes{
Expand All @@ -267,8 +267,8 @@ func TestBodySchema_DependentBodySchema_missingDependentAttr(t *testing.T) {
},
},
})
if !ok {
t.Fatal("expected to find first body schema for missing keys")
if result != LookupPartiallySuccessful {
t.Fatalf("expected to find first body schema for missing keys; reported: %q", result)
}
if diff := cmp.Diff(firstDepBody, bodySchema, ctydebug.CmpOptions); diff != "" {
t.Fatalf("mismatching body schema: %s", diff)
Expand Down Expand Up @@ -418,8 +418,8 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
Attributes: tc.attributes,
},
}
bodySchema, _, ok := tc.schema.DependentBodySchema(block)
if !ok {
bodySchema, _, result := tc.schema.DependentBodySchema(block)
if result != LookupSuccessful {
t.Fatalf("expected to find body schema for given block with %d attributes",
len(tc.attributes))
}
Expand All @@ -430,13 +430,86 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
}
}

func TestBodySchema_DependentBodySchema_partialMergeFailure(t *testing.T) {
testSchema := NewBlockSchema(&schema.BlockSchema{
Labels: []*schema.LabelSchema{
{
Name: "type",
IsDepKey: true,
},
},
Body: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"count": {
Constraint: schema.AnyExpression{OfType: cty.Number},
},
},
},
DependentBody: map[schema.SchemaKey]*schema.BodySchema{
schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{
{
Index: 0,
Value: "terraform_remote_state",
},
},
}): {
Attributes: map[string]*schema.AttributeSchema{
"first": {
Constraint: schema.LiteralType{Type: cty.String},
},
"backend": {
Constraint: schema.AnyExpression{OfType: cty.String},
IsDepKey: true,
},
},
},
schema.NewSchemaKey(schema.DependencyKeys{
Attributes: []schema.AttributeDependent{
{
Name: "backend",
Expr: schema.ExpressionValue{
Static: cty.StringVal("remote"),
},
},
},
}): {
Attributes: map[string]*schema.AttributeSchema{
"second": {Constraint: schema.LiteralType{Type: cty.String}},
},
},
},
})

block := &hcl.Block{
Labels: []string{"terraform_remote_state"},
Body: &hclsyntax.Body{
Attributes: map[string]*hclsyntax.Attribute{
"backend": {
Name: "backend",
Expr: &hclsyntax.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{Name: "referencestep"},
},
},
},
},
},
}

_, result := MergeBlockBodySchemas(block, testSchema.BlockSchema)
if result != LookupPartiallySuccessful {
t.Fatal("expected partially failed dependent body lookup to fail")
}
}

func TestBodySchema_DependentBodySchema_label_notFound(t *testing.T) {
block := &hcl.Block{
Labels: []string{"test", "mycloud"},
Body: hcl.EmptyBody(),
}
_, _, ok := testSchemaWithLabels.DependentBodySchema(block)
if ok {
_, _, result := testSchemaWithLabels.DependentBodySchema(block)
if result != LookupFailed {
t.Fatal("expected not to find body schema for 'mycloud' 2nd label")
}
}
Expand All @@ -446,8 +519,8 @@ func TestBodySchema_DependentBodySchema_label_storedUnsorted(t *testing.T) {
Labels: []string{"complexcloud", "pumpkin"},
Body: hcl.EmptyBody(),
}
bodySchema, _, ok := testSchemaWithLabels.DependentBodySchema(block)
if !ok {
bodySchema, _, result := testSchemaWithLabels.DependentBodySchema(block)
if result != LookupSuccessful {
t.Fatal("expected to find body schema stored with unsorted keys")
}
expectedSchema := &schema.BodySchema{
Expand All @@ -465,8 +538,8 @@ func TestBodySchema_DependentBodySchema_label_lookupUnsorted(t *testing.T) {
Labels: []string{"apple", "crazycloud"},
Body: hcl.EmptyBody(),
}
_, _, ok := testSchemaWithLabels.DependentBodySchema(block)
if ok {
_, _, result := testSchemaWithLabels.DependentBodySchema(block)
if result != LookupFailed {
t.Fatal("expected to not find body schema based on wrongly sorted labels")
}
}
Expand Down
4 changes: 2 additions & 2 deletions decoder/internal/walker/walker.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ func Walk(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema, w
var blockBodySchema schema.Schema = nil
bSchema, ok := nodeSchema.(*schema.BlockSchema)
if ok && bSchema.Body != nil {
mergedSchema, ok := schemahelper.MergeBlockBodySchemas(nodeType.AsHCLBlock(), bSchema)
if !ok {
mergedSchema, result := schemahelper.MergeBlockBodySchemas(nodeType.AsHCLBlock(), bSchema)
if result == schemahelper.LookupFailed || result == schemahelper.LookupPartiallySuccessful {
ctx = schemacontext.WithUnknownSchema(ctx)
}
blockBodySchema = mergedSchema
Expand Down
4 changes: 2 additions & 2 deletions decoder/links.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func (d *PathDecoder) linksInBody(body *hclsyntax.Body, bodySchema *schema.BodyS

// Currently only block bodies have links associated
if block.Body != nil {
depSchema, dk, ok := schemahelper.NewBlockSchema(blockSchema).DependentBodySchema(block.AsHCLBlock())
if ok && depSchema.DocsLink != nil {
depSchema, dk, result := schemahelper.NewBlockSchema(blockSchema).DependentBodySchema(block.AsHCLBlock())
if (result == schemahelper.LookupSuccessful || result == schemahelper.LookupPartiallySuccessful || result == schemahelper.NoDependentKeys) && depSchema.DocsLink != nil {
link := depSchema.DocsLink
u, err := d.docsURL(link.URL, "documentLink")
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions decoder/reference_targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ func (d *PathDecoder) decodeReferenceTargetsForBody(body hcl.Body, parentBlock *
}
}

depSchema, _, ok := schemahelper.NewBlockSchema(bSchema).DependentBodySchema(blk.Block)
if ok {
depSchema, _, result := schemahelper.NewBlockSchema(bSchema).DependentBodySchema(blk.Block)
if result == schemahelper.LookupSuccessful {
fullSchema := depSchema
if bSchema.Address.BodyAsData {
mergedSchema, _ := schemahelper.MergeBlockBodySchemas(blk.Block, bSchema)
Expand Down

0 comments on commit fc59723

Please sign in to comment.