Skip to content

Commit b6549e0

Browse files
authored
chore: utilities to handle labels and selectors (#881)
Will be used in #817
1 parent 83af976 commit b6549e0

File tree

4 files changed

+147
-0
lines changed

4 files changed

+147
-0
lines changed
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package datasourceutil
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/diag"
7+
)
8+
9+
// GetOneResultForLabelSelector verifies that only one item is returned for a label selector. If none or >1 are returned
10+
// it returns an error [diag.Diagnostic].
11+
func GetOneResultForLabelSelector[T any](resourceName string, items []*T, labelSelector string) (*T, diag.Diagnostic) {
12+
if len(items) == 0 {
13+
return nil, diag.NewErrorDiagnostic(fmt.Sprintf("No %s found for label selector", resourceName), fmt.Sprintf(
14+
"No %s found for label selector.\n\n"+
15+
"Label selector: %s\n",
16+
resourceName,
17+
labelSelector,
18+
))
19+
}
20+
if len(items) > 1 {
21+
return nil, diag.NewErrorDiagnostic(
22+
fmt.Sprintf("More than one %s found for label selector", resourceName),
23+
fmt.Sprintf(
24+
"More than one %s found for label selector.\n\n"+
25+
"Label selector: %s\n",
26+
resourceName,
27+
labelSelector,
28+
),
29+
)
30+
}
31+
32+
return items[0], nil
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package datasourceutil
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/diag"
8+
)
9+
10+
func TestGetOneResultForLabelSelector(t *testing.T) {
11+
type item struct{ ID int }
12+
13+
type args struct {
14+
resourceName string
15+
items []*item
16+
labelSelector string
17+
}
18+
type testCase struct {
19+
name string
20+
args args
21+
want *item
22+
diagnostic diag.Diagnostic
23+
}
24+
tests := []testCase{
25+
{
26+
name: "one item",
27+
args: args{
28+
resourceName: "item",
29+
items: []*item{{ID: 1}},
30+
labelSelector: "foo=bar",
31+
},
32+
want: &item{ID: 1},
33+
diagnostic: nil,
34+
},
35+
{
36+
name: "zero items",
37+
args: args{
38+
resourceName: "item",
39+
items: []*item{},
40+
labelSelector: "foo=bar",
41+
},
42+
want: nil,
43+
diagnostic: diag.NewErrorDiagnostic("No item found for label selector", "No item found for label selector.\n\nLabel selector: foo=bar\n"),
44+
},
45+
{
46+
name: "two items",
47+
args: args{
48+
resourceName: "item",
49+
items: []*item{{ID: 1}, {ID: 2}},
50+
labelSelector: "foo=bar",
51+
},
52+
want: nil,
53+
diagnostic: diag.NewErrorDiagnostic("More than one item found for label selector", "More than one item found for label selector.\n\nLabel selector: foo=bar\n"),
54+
},
55+
}
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
gotItem, gotDiagnostic := GetOneResultForLabelSelector(tt.args.resourceName, tt.args.items, tt.args.labelSelector)
59+
if !reflect.DeepEqual(gotItem, tt.want) {
60+
t.Errorf("GetOneResultForLabelSelector() got = %v, want %v", gotItem, tt.want)
61+
}
62+
if !reflect.DeepEqual(gotDiagnostic, tt.diagnostic) {
63+
t.Errorf("GetOneResultForLabelSelector() got1 = %v, want %v", gotDiagnostic, tt.diagnostic)
64+
}
65+
})
66+
}
67+
}

internal/util/resourceutil/labels.go

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package resourceutil
33
import (
44
"context"
55

6+
"github.com/hashicorp/terraform-plugin-framework/diag"
67
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
78
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
89
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -47,3 +48,15 @@ func LabelsSchema() schema.MapAttribute {
4748
},
4849
}
4950
}
51+
52+
// LabelsMapValueFrom prepare the labels from the API to be assigned into the resource model.
53+
//
54+
// In the resource schemas, labels can be null, but the API always returns an empty object for labels.
55+
// This causes a conflict in the Terraform Data Consistency check. This method handles empty label
56+
// objects by instead returning a null map.
57+
func LabelsMapValueFrom(ctx context.Context, in map[string]string) (types.Map, diag.Diagnostics) {
58+
if len(in) > 0 {
59+
return types.MapValueFrom(ctx, types.StringType, in)
60+
}
61+
return types.MapNull(types.StringType), nil
62+
}

internal/util/resourceutil/labels_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package resourceutil
22

33
import (
44
"context"
5+
"reflect"
56
"sort"
67
"testing"
78

@@ -79,3 +80,36 @@ func TestLabelsValidator_ValidateMap(t *testing.T) {
7980
})
8081
}
8182
}
83+
84+
func TestLabelsMapValueFrom(t *testing.T) {
85+
tests := []struct {
86+
name string
87+
in map[string]string
88+
want types.Map
89+
diags diag.Diagnostics
90+
}{
91+
{
92+
name: "Map with Labels",
93+
in: map[string]string{"foo": "bar"},
94+
want: types.MapValueMust(types.StringType, map[string]attr.Value{"foo": types.StringValue("bar")}),
95+
diags: nil,
96+
},
97+
{
98+
name: "Empty Map",
99+
in: map[string]string{},
100+
want: types.MapNull(types.StringType),
101+
diags: nil,
102+
},
103+
}
104+
for _, tt := range tests {
105+
t.Run(tt.name, func(t *testing.T) {
106+
labels, diags := LabelsMapValueFrom(context.Background(), tt.in)
107+
if !reflect.DeepEqual(labels, tt.want) {
108+
t.Errorf("LabelsMapValueFrom() got = %v, want %v", labels, tt.want)
109+
}
110+
if !reflect.DeepEqual(diags, tt.diags) {
111+
t.Errorf("LabelsMapValueFrom() got1 = %v, want %v", diags, tt.diags)
112+
}
113+
})
114+
}
115+
}

0 commit comments

Comments
 (0)