Skip to content

Commit a60116c

Browse files
authored
Fix multivalue in match expression (#15)
1 parent cc46cbb commit a60116c

3 files changed

Lines changed: 437 additions & 2 deletions

File tree

internal/provider/node_policy_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,143 @@ func TestNodePolicyResourceModel(t *testing.T) {
296296
}
297297
})
298298

299+
// Test LabelSelector with multiple MatchExpressions and multiple values
300+
t.Run("LabelSelector_WithMultipleMatchExpressions", func(t *testing.T) {
301+
selector := &LabelSelector{
302+
MatchLabels: types.MapValueMust(types.StringType, map[string]attr.Value{
303+
"app": types.StringValue("api"),
304+
"env": types.StringValue("prod"),
305+
}),
306+
MatchExpressions: types.ListValueMust(
307+
types.ObjectType{
308+
AttrTypes: map[string]attr.Type{
309+
"key": types.StringType,
310+
"operator": types.StringType,
311+
"values": types.ListType{ElemType: types.StringType},
312+
},
313+
},
314+
[]attr.Value{
315+
types.ObjectValueMust(
316+
map[string]attr.Type{
317+
"key": types.StringType,
318+
"operator": types.StringType,
319+
"values": types.ListType{ElemType: types.StringType},
320+
},
321+
map[string]attr.Value{
322+
"key": types.StringValue("instanceGenerations"),
323+
"operator": types.StringValue("Gt"),
324+
"values": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("4")}),
325+
},
326+
),
327+
types.ObjectValueMust(
328+
map[string]attr.Type{
329+
"key": types.StringType,
330+
"operator": types.StringType,
331+
"values": types.ListType{ElemType: types.StringType},
332+
},
333+
map[string]attr.Value{
334+
"key": types.StringValue("instanceFamily"),
335+
"operator": types.StringValue("In"),
336+
"values": types.ListValueMust(types.StringType, []attr.Value{
337+
types.StringValue("m5"),
338+
types.StringValue("m6i"),
339+
types.StringValue("m7i"),
340+
}),
341+
},
342+
),
343+
types.ObjectValueMust(
344+
map[string]attr.Type{
345+
"key": types.StringType,
346+
"operator": types.StringType,
347+
"values": types.ListType{ElemType: types.StringType},
348+
},
349+
map[string]attr.Value{
350+
"key": types.StringValue("zone"),
351+
"operator": types.StringValue("NotIn"),
352+
"values": types.ListValueMust(types.StringType, []attr.Value{
353+
types.StringValue("us-west-2a"),
354+
types.StringValue("us-west-2b"),
355+
}),
356+
},
357+
),
358+
},
359+
),
360+
}
361+
362+
// Test toProto
363+
ctx := context.Background()
364+
proto, err := selector.toProto(ctx)
365+
if err != nil {
366+
t.Fatalf("Expected no error, got %v", err)
367+
}
368+
if proto == nil {
369+
t.Fatal("Expected non-nil proto")
370+
}
371+
if len(proto.MatchLabels) != 2 {
372+
t.Errorf("Expected 2 match labels, got %d", len(proto.MatchLabels))
373+
}
374+
if proto.MatchLabels["app"] != "api" {
375+
t.Errorf("Expected app=api, got %s", proto.MatchLabels["app"])
376+
}
377+
if proto.MatchLabels["env"] != "prod" {
378+
t.Errorf("Expected env=prod, got %s", proto.MatchLabels["env"])
379+
}
380+
if len(proto.MatchExpressions) != 3 {
381+
t.Fatalf("Expected 3 match expressions, got %d", len(proto.MatchExpressions))
382+
}
383+
384+
// Check first expression (Gt with single value)
385+
expr1 := proto.MatchExpressions[0]
386+
if expr1.Key != "instanceGenerations" {
387+
t.Errorf("Expected key=instanceGenerations, got %s", expr1.Key)
388+
}
389+
if expr1.Operator != 5 { // GT = 5
390+
t.Errorf("Expected operator=GT (5), got %d", expr1.Operator)
391+
}
392+
if len(expr1.Values) != 1 {
393+
t.Errorf("Expected 1 value, got %d", len(expr1.Values))
394+
}
395+
if expr1.Values[0] != "4" {
396+
t.Errorf("Expected value '4', got %s", expr1.Values[0])
397+
}
398+
399+
// Check second expression (In with multiple values)
400+
expr2 := proto.MatchExpressions[1]
401+
if expr2.Key != "instanceFamily" {
402+
t.Errorf("Expected key=instanceFamily, got %s", expr2.Key)
403+
}
404+
if expr2.Operator != 1 { // IN = 1
405+
t.Errorf("Expected operator=IN (1), got %d", expr2.Operator)
406+
}
407+
if len(expr2.Values) != 3 {
408+
t.Errorf("Expected 3 values, got %d", len(expr2.Values))
409+
}
410+
expectedValues := []string{"m5", "m6i", "m7i"}
411+
for i, expected := range expectedValues {
412+
if expr2.Values[i] != expected {
413+
t.Errorf("Expected value[%d]='%s', got '%s'", i, expected, expr2.Values[i])
414+
}
415+
}
416+
417+
// Check third expression (NotIn with multiple values)
418+
expr3 := proto.MatchExpressions[2]
419+
if expr3.Key != "zone" {
420+
t.Errorf("Expected key=zone, got %s", expr3.Key)
421+
}
422+
if expr3.Operator != 2 { // NOT_IN = 2
423+
t.Errorf("Expected operator=NOT_IN (2), got %d", expr3.Operator)
424+
}
425+
if len(expr3.Values) != 2 {
426+
t.Errorf("Expected 2 values, got %d", len(expr3.Values))
427+
}
428+
expectedZones := []string{"us-west-2a", "us-west-2b"}
429+
for i, expected := range expectedZones {
430+
if expr3.Values[i] != expected {
431+
t.Errorf("Expected value[%d]='%s', got '%s'", i, expected, expr3.Values[i])
432+
}
433+
}
434+
})
435+
299436
// Test DisruptionPolicy with nested budgets (full toProto conversion)
300437
t.Run("DisruptionPolicy_WithBudgets", func(t *testing.T) {
301438
policy := &DisruptionPolicy{

internal/provider/workload_policy_target.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,8 +644,55 @@ func (m *LabelSelector) fromProto(selector *apiv1.LabelSelector) {
644644
if m == nil {
645645
m = &LabelSelector{}
646646
}
647-
m.MatchLabels = types.MapValueMust(types.StringType, fromStringMap(selector.MatchLabels))
648-
m.MatchExpressions = types.ListValueMust(types.StringType, fromElementList(selector.MatchExpressions, MatchExpression{}.AttrTypes()))
647+
648+
// Handle match_labels: if empty, set to null instead of empty map
649+
if len(selector.MatchLabels) == 0 {
650+
m.MatchLabels = types.MapNull(types.StringType)
651+
} else {
652+
m.MatchLabels = types.MapValueMust(types.StringType, fromStringMap(selector.MatchLabels))
653+
}
654+
655+
// Manually convert match expressions from proto to Terraform types
656+
var matchExpressions []attr.Value
657+
for _, expr := range selector.MatchExpressions {
658+
// Convert operator enum to string
659+
var operatorStr string
660+
switch expr.Operator {
661+
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_IN:
662+
operatorStr = "In"
663+
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_NOT_IN:
664+
operatorStr = "NotIn"
665+
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_EXISTS:
666+
operatorStr = "Exists"
667+
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_DOES_NOT_EXIST:
668+
operatorStr = "DoesNotExist"
669+
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_GT:
670+
operatorStr = "Gt"
671+
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_LT:
672+
operatorStr = "Lt"
673+
}
674+
675+
// Convert values to Terraform list
676+
values := types.ListValueMust(types.StringType, fromStringList(expr.Values))
677+
678+
// Build the match expression object
679+
matchExpr := types.ObjectValueMust(
680+
MatchExpression{}.AttrTypes(),
681+
map[string]attr.Value{
682+
"key": types.StringValue(expr.Key),
683+
"operator": types.StringValue(operatorStr),
684+
"values": values,
685+
},
686+
)
687+
matchExpressions = append(matchExpressions, matchExpr)
688+
}
689+
690+
// Handle match_expressions: if empty, set to null instead of empty list
691+
if len(matchExpressions) == 0 {
692+
m.MatchExpressions = types.ListNull(types.ObjectType{AttrTypes: MatchExpression{}.AttrTypes()})
693+
} else {
694+
m.MatchExpressions = types.ListValueMust(types.ObjectType{AttrTypes: MatchExpression{}.AttrTypes()}, matchExpressions)
695+
}
649696
}
650697

651698
func (m *RegexPattern) fromProto(pattern *apiv1.RegexPattern) {

0 commit comments

Comments
 (0)