Skip to content

Commit 2563b07

Browse files
authored
Allow list events with only warnings (#37415)
This updates the event processing logic. Terraform now allows error and warning diagnostics for list events with all other fields set to null. The combination of a warning with a valid result is still possible.
1 parent 0c63fb2 commit 2563b07

File tree

4 files changed

+432
-84
lines changed

4 files changed

+432
-84
lines changed

internal/plugin/grpc_provider.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,12 +1350,23 @@ func (p *GRPCProvider) ListResource(r providers.ListResourceRequest) providers.L
13501350
break
13511351
}
13521352

1353+
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(event.Diagnostic))
1354+
if resp.Diagnostics.HasErrors() {
1355+
// If we have errors, we stop processing and return early
1356+
break
1357+
}
1358+
1359+
if resp.Diagnostics.HasWarnings() &&
1360+
(event.Identity == nil || event.Identity.IdentityData == nil) {
1361+
// If we have warnings but no identity data, we continue with the next event
1362+
continue
1363+
}
1364+
13531365
obj := map[string]cty.Value{
13541366
"display_name": cty.StringVal(event.DisplayName),
13551367
"state": cty.NullVal(resourceSchema.Body.ImpliedType()),
13561368
"identity": cty.NullVal(resourceSchema.Identity.ImpliedType()),
13571369
}
1358-
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(event.Diagnostic))
13591370

13601371
// Handle identity data - it must be present
13611372
if event.Identity == nil || event.Identity.IdentityData == nil {
@@ -1381,11 +1392,11 @@ func (p *GRPCProvider) ListResource(r providers.ListResourceRequest) providers.L
13811392
}
13821393

13831394
if resp.Diagnostics.HasErrors() {
1395+
// If validation errors occurred, we stop processing and return early
13841396
break
13851397
}
13861398

13871399
results = append(results, cty.ObjectVal(obj))
1388-
13891400
}
13901401

13911402
// The provider result of a list resource is always a list, but

internal/plugin/grpc_provider_test.go

Lines changed: 203 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,60 +2737,223 @@ func TestGRPCProvider_ListResource_Error(t *testing.T) {
27372737
}
27382738

27392739
func TestGRPCProvider_ListResource_Diagnostics(t *testing.T) {
2740-
client := mockProviderClient(t)
2741-
p := &GRPCProvider{
2742-
client: client,
2743-
ctx: context.Background(),
2740+
configVal := cty.ObjectVal(map[string]cty.Value{
2741+
"config": cty.ObjectVal(map[string]cty.Value{
2742+
"filter_attr": cty.StringVal("filter-value"),
2743+
}),
2744+
})
2745+
request := providers.ListResourceRequest{
2746+
TypeName: "list",
2747+
Config: configVal,
2748+
Limit: 100,
27442749
}
27452750

2746-
// Create a mock stream client that will return a resource event with diagnostics
2747-
mockStream := &mockListResourceStreamClient{
2748-
events: []*proto.ListResource_Event{
2749-
{
2750-
DisplayName: "Test Resource With Warning",
2751-
Identity: &proto.ResourceIdentityData{
2752-
IdentityData: &proto.DynamicValue{
2753-
Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
2751+
testCases := []struct {
2752+
name string
2753+
events []*proto.ListResource_Event
2754+
expectedCount int
2755+
expectedDiags int
2756+
expectedWarns int // subset of expectedDiags
2757+
}{
2758+
{
2759+
"no events",
2760+
[]*proto.ListResource_Event{},
2761+
0,
2762+
0,
2763+
0,
2764+
},
2765+
{
2766+
"single event no diagnostics",
2767+
[]*proto.ListResource_Event{
2768+
{
2769+
DisplayName: "Test Resource",
2770+
Identity: &proto.ResourceIdentityData{
2771+
IdentityData: &proto.DynamicValue{
2772+
Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
2773+
},
27542774
},
27552775
},
2756-
Diagnostic: []*proto.Diagnostic{
2757-
{
2758-
Severity: proto.Diagnostic_WARNING,
2759-
Summary: "Test warning",
2760-
Detail: "This is a test warning",
2776+
},
2777+
1,
2778+
0,
2779+
0,
2780+
},
2781+
{
2782+
"event with warning",
2783+
[]*proto.ListResource_Event{
2784+
{
2785+
DisplayName: "Test Resource",
2786+
Identity: &proto.ResourceIdentityData{
2787+
IdentityData: &proto.DynamicValue{
2788+
Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
2789+
},
2790+
},
2791+
Diagnostic: []*proto.Diagnostic{
2792+
{
2793+
Severity: proto.Diagnostic_WARNING,
2794+
Summary: "Test warning",
2795+
Detail: "Warning detail",
2796+
},
2797+
},
2798+
},
2799+
},
2800+
1,
2801+
1,
2802+
1,
2803+
},
2804+
{
2805+
"only a warning",
2806+
[]*proto.ListResource_Event{
2807+
{
2808+
Diagnostic: []*proto.Diagnostic{
2809+
{
2810+
Severity: proto.Diagnostic_WARNING,
2811+
Summary: "Test warning",
2812+
Detail: "Warning detail",
2813+
},
2814+
},
2815+
},
2816+
},
2817+
0,
2818+
1,
2819+
1,
2820+
},
2821+
{
2822+
"only an error",
2823+
[]*proto.ListResource_Event{
2824+
{
2825+
Diagnostic: []*proto.Diagnostic{
2826+
{
2827+
Severity: proto.Diagnostic_ERROR,
2828+
Summary: "Test error",
2829+
Detail: "Error detail",
2830+
},
27612831
},
27622832
},
27632833
},
2834+
0,
2835+
1,
2836+
0,
2837+
},
2838+
{
2839+
"event with error",
2840+
[]*proto.ListResource_Event{
2841+
{
2842+
DisplayName: "Test Resource",
2843+
Identity: &proto.ResourceIdentityData{
2844+
IdentityData: &proto.DynamicValue{
2845+
Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
2846+
},
2847+
},
2848+
Diagnostic: []*proto.Diagnostic{
2849+
{
2850+
Severity: proto.Diagnostic_ERROR,
2851+
Summary: "Test error",
2852+
Detail: "Error detail",
2853+
},
2854+
},
2855+
},
2856+
},
2857+
0,
2858+
1,
2859+
0,
2860+
},
2861+
{
2862+
"multiple events mixed diagnostics",
2863+
[]*proto.ListResource_Event{
2864+
{
2865+
DisplayName: "Resource 1",
2866+
Identity: &proto.ResourceIdentityData{
2867+
IdentityData: &proto.DynamicValue{
2868+
Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
2869+
},
2870+
},
2871+
Diagnostic: []*proto.Diagnostic{
2872+
{
2873+
Severity: proto.Diagnostic_WARNING,
2874+
Summary: "Warning 1",
2875+
Detail: "Warning detail 1",
2876+
},
2877+
},
2878+
},
2879+
{
2880+
DisplayName: "Resource 2",
2881+
Identity: &proto.ResourceIdentityData{
2882+
IdentityData: &proto.DynamicValue{
2883+
Msgpack: []byte("\x81\xa7id_attr\xa4id-2"),
2884+
},
2885+
},
2886+
},
2887+
{
2888+
DisplayName: "Resource 3",
2889+
Identity: &proto.ResourceIdentityData{
2890+
IdentityData: &proto.DynamicValue{
2891+
Msgpack: []byte("\x81\xa7id_attr\xa4id-3"),
2892+
},
2893+
},
2894+
Diagnostic: []*proto.Diagnostic{
2895+
{
2896+
Severity: proto.Diagnostic_ERROR,
2897+
Summary: "Error 1",
2898+
Detail: "Error detail 1",
2899+
},
2900+
{
2901+
Severity: proto.Diagnostic_WARNING,
2902+
Summary: "Warning 2",
2903+
Detail: "Warning detail 2",
2904+
},
2905+
},
2906+
},
2907+
{ // This event will never be reached
2908+
DisplayName: "Resource 4",
2909+
Identity: &proto.ResourceIdentityData{
2910+
IdentityData: &proto.DynamicValue{
2911+
Msgpack: []byte("\x81\xa7id_attr\xa4id-4"),
2912+
},
2913+
},
2914+
},
2915+
},
2916+
2,
2917+
3,
2918+
2,
27642919
},
27652920
}
27662921

2767-
client.EXPECT().ListResource(
2768-
gomock.Any(),
2769-
gomock.Any(),
2770-
).Return(mockStream, nil)
2922+
for _, tc := range testCases {
2923+
t.Run(tc.name, func(t *testing.T) {
2924+
client := mockProviderClient(t)
2925+
p := &GRPCProvider{
2926+
client: client,
2927+
ctx: context.Background(),
2928+
}
27712929

2772-
// Create the request
2773-
configVal := cty.ObjectVal(map[string]cty.Value{
2774-
"config": cty.ObjectVal(map[string]cty.Value{
2775-
"filter_attr": cty.StringVal("filter-value"),
2776-
}),
2777-
})
2778-
request := providers.ListResourceRequest{
2779-
TypeName: "list",
2780-
Config: configVal,
2781-
Limit: 100,
2782-
}
2930+
mockStream := &mockListResourceStreamClient{
2931+
events: tc.events,
2932+
}
27832933

2784-
resp := p.ListResource(request)
2785-
checkDiags(t, resp.Diagnostics)
2934+
client.EXPECT().ListResource(
2935+
gomock.Any(),
2936+
gomock.Any(),
2937+
).Return(mockStream, nil)
27862938

2787-
data := resp.Result.AsValueMap()
2788-
if _, ok := data["data"]; !ok {
2789-
t.Fatal("Expected 'data' key in result")
2790-
}
2939+
resp := p.ListResource(request)
2940+
2941+
result := resp.Result.AsValueMap()
2942+
nResults := result["data"].LengthInt()
2943+
if nResults != tc.expectedCount {
2944+
t.Fatalf("Expected %d results, got %d", tc.expectedCount, nResults)
2945+
}
27912946

2792-
if !resp.Diagnostics.HasWarnings() {
2793-
t.Fatal("Expected warning diagnostics, but got none")
2947+
nDiagnostics := len(resp.Diagnostics)
2948+
if nDiagnostics != tc.expectedDiags {
2949+
t.Fatalf("Expected %d diagnostics, got %d", tc.expectedDiags, nDiagnostics)
2950+
}
2951+
2952+
nWarnings := len(resp.Diagnostics.Warnings())
2953+
if nWarnings != tc.expectedWarns {
2954+
t.Fatalf("Expected %d warnings, got %d", tc.expectedWarns, nWarnings)
2955+
}
2956+
})
27942957
}
27952958
}
27962959

internal/plugin6/grpc_provider.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,12 +1345,23 @@ func (p *GRPCProvider) ListResource(r providers.ListResourceRequest) providers.L
13451345
break
13461346
}
13471347

1348+
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(event.Diagnostic))
1349+
if resp.Diagnostics.HasErrors() {
1350+
// If we have errors, we stop processing and return early
1351+
break
1352+
}
1353+
1354+
if resp.Diagnostics.HasWarnings() &&
1355+
(event.Identity == nil || event.Identity.IdentityData == nil) {
1356+
// If we have warnings but no identity data, we continue with the next event
1357+
continue
1358+
}
1359+
13481360
obj := map[string]cty.Value{
13491361
"display_name": cty.StringVal(event.DisplayName),
13501362
"state": cty.NullVal(resourceSchema.Body.ImpliedType()),
13511363
"identity": cty.NullVal(resourceSchema.Identity.ImpliedType()),
13521364
}
1353-
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(event.Diagnostic))
13541365

13551366
// Handle identity data - it must be present
13561367
if event.Identity == nil || event.Identity.IdentityData == nil {
@@ -1376,11 +1387,11 @@ func (p *GRPCProvider) ListResource(r providers.ListResourceRequest) providers.L
13761387
}
13771388

13781389
if resp.Diagnostics.HasErrors() {
1390+
// If validation errors occurred, we stop processing and return early
13791391
break
13801392
}
13811393

13821394
results = append(results, cty.ObjectVal(obj))
1383-
13841395
}
13851396

13861397
// The provider result of a list resource is always a list, but

0 commit comments

Comments
 (0)