Skip to content

plugin/external/convert.go:mapToStruct silently drops Outputs on structpb.NewStruct errors #537

@intel352

Description

@intel352

Bug

plugin/external/convert.go:20-30 mapToStruct falls back to an empty *structpb.Struct{} when structpb.NewStruct(m) returns an error:

```go
func mapToStruct(m map[string]any) *structpb.Struct {
if m == nil {
return nil
}
s, err := structpb.NewStruct(m)
if err != nil {
return &structpb.Struct{} // silent drop — should propagate
}
return s
}
```

Result: providers whose ResourceOutput.Outputs or DiffResult.Changes carry types structpb cannot represent (chan, func, *time.Time without MarshalJSON, custom struct without map representation) silently drop those fields on the wire. Downstream consumers (state persistence, drift detection, cross-module substitution) see an empty Outputs map and treat it as "no changes" — masking real provider bugs.

Surfaced by

W-7 conformance suite — Scenario_DiffSurvivesGRPCRoundTrip (in iac/conformance/scenario_grpc_roundtrip.go) is intentionally stricter than this production wire so the underlying provider bug surfaces before the wire layer hides it. Reviewed in PR #535 by Copilot:
#535 (comment)

Fix sketch

Propagate the error rather than swallowing it:

```go
func mapToStruct(m map[string]any) (*structpb.Struct, error) {
if m == nil {
return nil, nil
}
s, err := structpb.NewStruct(m)
if err != nil {
return nil, fmt.Errorf("structpb.NewStruct: %w", err)
}
return s, nil
}
```

This is a signature-changing fix; the same shape exists in plugin/external/sdk/grpc_server.go:575. Both call sites need a coordinated bump. Estimate: small (5-10 file diff), but every caller needs to handle the error path. Not appropriate for W-7 scope; tracked here as the architecturally-correct counterpart to the conformance-suite strictness.

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions