Azure / Azure/golden

blockToCtyValue ignores Valuable.Values() and may panic during reflection

Open Beginner friendly
#96 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
3
Forks
13
PR merge metrics
No merged PRs in 30d

Description

## Summary

`golden.Valuable` declares a `Values() map[string]cty.Value` extension point, and Azure/grept already implements it for blocks such as `HttpDatasource` and `BaseFix`. However, `blockToCtyValue` currently always calls the reflection-based `Value(b)` and never consults `Valuable`.

This is more than an unused customization: reflection can panic before a block has a chance to provide its valid public cty representation. A common case is a decoded nested-block slice whose elements contain objects with different shapes.

Affected code on current `main` (`6e9a3fc2760e6f8440e4dfe8d0b1360886588bdd`):

```go
func blockToCtyValue(b Block) cty.Value {
blockValues := map[string]cty.Value{}
baseCtyValues := b.BaseValues()
ctyValues := Value(b)
// ...
}
```

Related grept usage:

- https://github.com/Azure/grept/blob/main/pkg/data_http.go
- https://github.com/Azure/grept/blob/main/pkg/fix.go

## Reproduction

Add the following declarations and test to `block_test.go`. It uses the test package's existing `BaseData` and Golden's `BaseBlock`:

```go
var _ Valuable = (*ValuableData)(nil)

type ValuableData struct {
*BaseData
*BaseBlock
ReflectedValues []cty.Value `hcl:"reflected_values"`
}

func (d *ValuableData) Type() string {
return "valuable"
}

func (d *ValuableData) ExecuteDuringPlan() error {
return nil
}

func (d *ValuableData) Values() map[string]cty.Value {
return map[string]cty.Value{
"custom_value": cty.StringVal("from Values"),
}
}

func TestBlockToCtyValueUsesValuableValues(t *testing.T) {
block := &ValuableData{
BaseData: &BaseData{},
BaseBlock: &BaseBlock{id: "block-id"},
ReflectedValues: []cty.Value{
cty.ObjectVal(map[string]cty.Value{"first": cty.True}),
cty.ObjectVal(map[string]cty.Value{"second": cty.True}),
},
}

value := blockToCtyValue(block)

assert.Equal(t, "from Values", value.GetAttr("custom_value").AsString())
assert.False(t, value.Type().HasAttribute("reflected_values"))
assert.Equal(t, "block-id", value.GetAttr("id").AsString())
}
```

Run:

```text
go test ./... -run TestBlockToCtyValueUsesValuableValues -count=1
```

## Actual behavior

The test panics inside `Value(b)` / `ToCtyValue` before `Values()` can be used:

```text
panic: inconsistent list element types
(cty.Object(map[string]cty.Type{"first":cty.Bool})
then cty.Object(map[string]cty.Type{"second":cty.Bool}))
```

## Expected behavior

When a block implements `Valuable`, Golden should use `Valuable.Values()` as the block's complete public cty representation and should not reflect that block. `BaseValues()` should still be merged afterward so Golden-owned metadata such as `id` remains available and keeps its existing precedence.

## Proposed fix

Make reflection and `Valuable` mutually exclusive:

```go
ctyValues := map[string]cty.Value{}
if valuable, ok := b.(Valuable); ok {
ctyValues = valuable.Values()
} else {
ctyValues = Value(b)
}
```

Then retain the existing merge order for `BaseValues()`.

It is important not to call `Value(b)` first and overwrite the result afterward: the reproduction demonstrates that reflection itself may panic, so late replacement does not solve the bug.

This should be backward compatible for blocks that do not implement `Valuable`, while making the already-declared interface usable as intended.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at blockToCtyValue and inspect how it currently combines Value(b) with BaseValues(). Add the reproduction and TestBlockToCtyValueUsesValuableValues in block_test.go, then run go test ./... -run TestBlockToCtyValueUsesValuableValues -count=1. Done means Valuable.Values() supplies the public representation without reflection, while the existing id metadata remains available with its current precedence.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
85/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.