Skip to content

Conversation

@Tzvonimir
Copy link
Contributor

@Tzvonimir Tzvonimir commented Nov 8, 2025

Summary by CodeRabbit

  • Tests

    • Added comprehensive tests for label selectors covering multiple match expressions, multiple values, empty-collection handling, and round-trip conversion to ensure fidelity between representations.
  • Bug Fixes

    • Improved parsing/serialization of complex label selection so match expressions are emitted and restored as structured objects and empty collections are represented as null where appropriate.

@Tzvonimir Tzvonimir self-assigned this Nov 8, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 8, 2025

Walkthrough

LabelSelector serialization/deserialization was reworked: fromProto now builds structured Terraform Object entries for MatchExpressions (key, operator, values) and MatchLabels empty collections become null. Tests added/extended to cover multiple MatchExpressions, multi-value lists, empty collections, and round-trip integrity.

Changes

Cohort / File(s) Summary
MatchExpressions deserialization
internal/provider/workload_policy_target.go
fromProto now maps each proto MatchExpression into a Terraform Object with attributes key, operator (enum → string), and values (string list). Empty expressions become null; non-empty set as List(ObjectType) instead of prior string-based representation.
LabelSelector test coverage
internal/provider/node_policy_test.go, internal/provider/workload_policy_target_test.go
Added/extended tests exercising LabelSelector.toProto and fromProto with multiple MatchExpressions (operators: In, NotIn, Exists, Gt, etc.), multi-value lists, empty-collection→null behavior, and a round-trip (toProto→fromProto) equality check.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant T as Tests
    participant P as protobuf LabelSelector
    participant M as WorkloadPolicyTarget.fromProto
    note right of M `#DDEBF7`: Change — build structured Object entries

    T->>P: construct LabelSelector proto (labels + matchExpressions)
    T->>M: call fromProto(proto)
    M->>M: if MatchLabels empty -> set null\nelse -> map to Terraform map
    M->>M: for each proto.matchExpression\n  map enum -> string operator\n  convert values -> Terraform string list\n  build Object { key, operator, values }
    M->>M: collect objects -> List(ObjectType) or null if empty
    M-->>T: return model with structured MatchExpressions and MatchLabels
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • Correctness and completeness of enum-to-string operator mapping (In, NotIn, Exists, DoesNotExist, Gt, Lt) and parity with toProto behavior.
    • Terraform object construction: attribute types, list encoding, and null vs empty handling.
    • Tests: verify coverage for edge cases (empty values, Exists/DoesNotExist, numeric comparison operators) and potential duplicate test blocks.

Possibly related PRs

  • Add more tests and fix nils #13 — modifies MatchExpressions parsing in workload_policy_target.go and adds overlapping LabelSelector tests (strong code-level overlap).

Suggested reviewers

  • f-leu

Poem

🐰 I hop through code with careful cheer,

Objects now bloom where strings were near.
Keys and ops and values line,
Tests ensure each hop’s design.
A carrot nod to parsing clear. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix multivalue in match expression' directly relates to the core changes—fixing how multiple values in match expressions are handled in LabelSelector conversion (toProto/fromProto).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tzvonimir/fix-multivalue-expr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/provider/workload_policy_target_test.go (1)

42-42: Update element type to match the schema structure.

This line creates an empty list with StringType elements, but based on the fix at line 648 and the schema definition, MatchExpressions should use ObjectType with MatchExpression attributes for consistency.

Apply this diff:

-		MatchExpressions: types.ListValueMust(types.StringType, []attr.Value{}), // Simplified for testing
+		MatchExpressions: types.ListValueMust(types.ObjectType{AttrTypes: MatchExpression{}.AttrTypes()}, []attr.Value{}), // Simplified for testing
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc46cbb and 4c5eca4.

📒 Files selected for processing (3)
  • internal/provider/node_policy_test.go (1 hunks)
  • internal/provider/workload_policy_target.go (1 hunks)
  • internal/provider/workload_policy_target_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/provider/workload_policy_target_test.go (1)
internal/provider/workload_policy_target.go (1)
  • LabelSelector (58-61)
internal/provider/node_policy_test.go (2)
internal/provider/workload_policy_target.go (1)
  • LabelSelector (58-61)
internal/gen/api/v1/common.pb.go (3)
  • LabelSelector (2966-2981)
  • LabelSelector (2996-2996)
  • LabelSelector (3011-3013)
🔇 Additional comments (3)
internal/provider/workload_policy_target_test.go (1)

126-258: Excellent test coverage for multiple match expressions.

The test comprehensively validates the fix for multivalue match expressions, covering:

  • Multiple expressions (In, NotIn, Exists)
  • Multiple values within expressions (3 values for In, 2 for NotIn)
  • Correct operator enum mappings (IN=1, NOT_IN=2, EXISTS=3)
  • Empty values for Exists operator
internal/provider/node_policy_test.go (1)

299-434: Excellent test coverage for node policy match expressions.

The test comprehensively validates multivalue match expressions for node policies, covering:

  • Multiple expressions (Gt, In, NotIn)
  • Multiple values within expressions (1 value for Gt, 3 for In, 2 for NotIn)
  • Correct operator enum mappings (GT=5, IN=1, NOT_IN=2)
  • Node-specific use cases (instanceGenerations, instanceFamily, zone)
internal/provider/workload_policy_target.go (1)

648-648: All verification checks passed—code change is correct.

The review comment is accurate:

  • fromElementList exists and correctly converts proto LabelSelectorRequirement values to Terraform object values using types.ObjectValueFrom
  • ✓ Schema definition (lines 94-118) defines match_expressions as ListNestedAttribute with nested objects
  • MatchExpression.AttrTypes() provides the correct object schema (key, operator, values)
  • toProto method (lines 533-552) expects and properly extracts these object attributes
  • ✓ The ObjectType change at line 648 correctly aligns with the schema and proto conversion flow

No concerns remain. The implementation is sound.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/provider/workload_policy_target.go (1)

648-684: Add default case to operator switch for robustness.

The operator switch statement lacks a default case. If an unknown or future operator enum value is received from the proto, operatorStr will remain empty, potentially causing silent failures or confusing behavior in Terraform state.

Consider adding a default case:

 		switch expr.Operator {
 		case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_IN:
 			operatorStr = "In"
 		case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_NOT_IN:
 			operatorStr = "NotIn"
 		case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_EXISTS:
 			operatorStr = "Exists"
 		case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_DOES_NOT_EXIST:
 			operatorStr = "DoesNotExist"
 		case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_GT:
 			operatorStr = "Gt"
 		case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_LT:
 			operatorStr = "Lt"
+		default:
+			operatorStr = "Unknown"
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c5eca4 and 32873c2.

📒 Files selected for processing (2)
  • internal/provider/workload_policy_target.go (1 hunks)
  • internal/provider/workload_policy_target_test.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/provider/workload_policy_target_test.go (1)
internal/provider/workload_policy_target.go (1)
  • LabelSelector (58-61)
internal/provider/workload_policy_target.go (1)
internal/gen/api/v1/common.pb.go (6)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_IN (208-208)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_NOT_IN (209-209)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_EXISTS (210-210)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_DOES_NOT_EXIST (211-211)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_GT (212-212)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_LT (213-213)
🪛 GitHub Actions: Tests
internal/provider/workload_policy_target_test.go

[error] 387-387: type assertion must be checked (forcetypeassert)

🪛 GitHub Check: Build
internal/provider/workload_policy_target_test.go

[failure] 388-388:
type assertion must be checked (forcetypeassert)


[failure] 387-387:
type assertion must be checked (forcetypeassert)

🔇 Additional comments (1)
internal/provider/workload_policy_target_test.go (1)

126-258: Excellent test coverage for multiple match expressions.

This test thoroughly validates the toProto conversion with multiple match expressions and multiple values, covering different operators (In, NotIn, Exists) and edge cases like empty values for the Exists operator.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32873c2 and 55cc9fa.

📒 Files selected for processing (2)
  • internal/provider/workload_policy_target.go (1 hunks)
  • internal/provider/workload_policy_target_test.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/provider/workload_policy_target.go (1)
internal/gen/api/v1/common.pb.go (6)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_IN (208-208)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_NOT_IN (209-209)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_EXISTS (210-210)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_DOES_NOT_EXIST (211-211)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_GT (212-212)
  • LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_LT (213-213)
internal/provider/workload_policy_target_test.go (1)
internal/provider/workload_policy_target.go (1)
  • LabelSelector (58-61)

Comment on lines +649 to +695
if len(selector.MatchLabels) == 0 {
m.MatchLabels = types.MapNull(types.StringType)
} else {
m.MatchLabels = types.MapValueMust(types.StringType, fromStringMap(selector.MatchLabels))
}

// Manually convert match expressions from proto to Terraform types
var matchExpressions []attr.Value
for _, expr := range selector.MatchExpressions {
// Convert operator enum to string
var operatorStr string
switch expr.Operator {
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_IN:
operatorStr = "In"
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_NOT_IN:
operatorStr = "NotIn"
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_EXISTS:
operatorStr = "Exists"
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_DOES_NOT_EXIST:
operatorStr = "DoesNotExist"
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_GT:
operatorStr = "Gt"
case apiv1.LabelSelectorOperator_LABEL_SELECTOR_OPERATOR_LT:
operatorStr = "Lt"
}

// Convert values to Terraform list
values := types.ListValueMust(types.StringType, fromStringList(expr.Values))

// Build the match expression object
matchExpr := types.ObjectValueMust(
MatchExpression{}.AttrTypes(),
map[string]attr.Value{
"key": types.StringValue(expr.Key),
"operator": types.StringValue(operatorStr),
"values": values,
},
)
matchExpressions = append(matchExpressions, matchExpr)
}

// Handle match_expressions: if empty, set to null instead of empty list
if len(matchExpressions) == 0 {
m.MatchExpressions = types.ListNull(types.ObjectType{AttrTypes: MatchExpression{}.AttrTypes()})
} else {
m.MatchExpressions = types.ListValueMust(types.ObjectType{AttrTypes: MatchExpression{}.AttrTypes()}, matchExpressions)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Nil selector leaves stale state
When selector is nil, we return before touching the new null-handling logic. Because m = nil only rebinds the local copy of the pointer, any existing selector struct (and its MatchLabels/MatchExpressions) survives unchanged. This means removing a selector server-side leaves the Terraform state full of the previous values. Please clear the fields instead of bailing early, for example:

-	if selector == nil {
-		m = nil
-		return
-	}
+	if selector == nil {
+		if m == nil {
+			return
+		}
+		m.MatchLabels = types.MapNull(types.StringType)
+		m.MatchExpressions = types.ListNull(types.ObjectType{AttrTypes: MatchExpression{}.AttrTypes()})
+		return
+	}

This way the state reflects the absence of a selector and we avoid perpetual drift.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
internal/provider/workload_policy_target.go around lines 649 to 695: if selector
is nil do not return early; instead explicitly clear the selector fields on m so
Terraform state is updated — set m.MatchLabels = types.MapNull(types.StringType)
and set m.MatchExpressions = types.ListNull(types.ObjectType{AttrTypes:
MatchExpression{}.AttrTypes()}) (then return). This replaces the early bail so
existing state is nulled when the server-side selector is removed.

@Tzvonimir Tzvonimir merged commit a60116c into main Nov 8, 2025
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants