Skip to content

Conversation

@xueli181114
Copy link
Contributor

@xueli181114 xueli181114 commented Dec 29, 2025

In this commit I

  • Removed useless code and the testing code
  • Refactored evaluator to support cel expression and jsonPATH field
  • Supported default value when field and cel expression match failure
  • Supported expression and complex field in capture

Summary by CodeRabbit

  • New Features

    • Capture configs support two modes: JSONPath field extraction or CEL expressions (mutually exclusive).
    • Unified value extraction used across preconditions, evaluations, and post-action payloads.
  • Documentation

    • Expanded guides with examples for Data Scopes, capture modes, JSONPath, CEL, and accessing captured/API values.
    • Updated post-action and payload examples to use direct string fields (including observed_time) and single-line expressions.
  • Configuration Changes

    • observed_time is now a direct string field (not a nested value object).

✏️ Tip: You can customize this high-level summary in your review settings.

@openshift-ci openshift-ci bot requested review from Mischulee and tirthct December 29, 2025 12:41
@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Walkthrough

This PR adds unified value extraction and capture modes across config loading, validation, criteria evaluation, and executors. It introduces ValueDef and extends CaptureField to support either a JSONPath field or a CEL expression (mutually exclusive), adds ParseValueDef, implements ExtractField and ExtractValue with FieldResult/ExtractValueResult types, and enforces capture validation (name + exactly one of field/expression, optional CEL compile). Executors (precondition and post-action) use ParseValueDef/ExtractValue; legacy nested-field traversal and several CEL helper functions were removed. Tests and README/docs updated accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant ConfigLoader as Config Loader
    participant Validator as Validator
    participant Executor as Executor
    participant Evaluator as Evaluator
    participant FieldExtractor as Field Extractor
    participant CELEvaluator as CEL Evaluator

    ConfigLoader->>Validator: provide parsed config (CaptureField, ValueDef)

    rect rgb(240,248,255)
    Note over Validator: Validation Phase
    Validator->>Validator: validateCaptureFields()
    loop per capture
        Validator->>Validator: ensure Name present
        Validator->>Validator: ensure exactly one of Field or Expression
        alt Expression & CEL available
            Validator->>CELEvaluator: compile/validate expression
            CELEvaluator-->>Validator: compile result
        end
    end
    end

    rect rgb(245,255,250)
    Note over Executor: Execution (Precondition)
    Executor->>Evaluator: init with API response
    loop per capture
        alt capture.field set
            Executor->>FieldExtractor: ExtractField(response, field)
            FieldExtractor-->>Executor: FieldResult{Value, Error}
        else capture.expression set
            Executor->>CELEvaluator: Evaluate(expression, context)
            CELEvaluator-->>Executor: CELResult{Value, Error}
        end
        Executor->>Executor: store captured value in exec context
    end
    end

    rect rgb(255,250,240)
    Note over Executor: Post-Action / Payload Building
    Executor->>Evaluator: ParseValueDef(valueDef)
    Evaluator->>Evaluator: choose ExtractValue(field, expression)
    alt field path
        Evaluator->>FieldExtractor: ExtractField(...)
    else expression path
        Evaluator->>CELEvaluator: Evaluate(...)
    end
    Evaluator-->>Executor: ExtractValueResult{Value, Source, Error}
    alt Value nil & Default present
        Executor->>Executor: use Default
    else
        Executor->>Executor: use Value
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

lgtm, approved

Suggested reviewers

  • tirthct

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.82% 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 references the main objective (refactoring evaluator for JSONPath and CEL support), but contains a typo ('refator' instead of 'refactor') and is somewhat vague about the specific changes.
✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@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 (6)
internal/criteria/field_evaluator.go (1)

51-68: Inconsistent value handling between Execute and FindResults.

The function executes the JSONPath query twice: first with Execute() (storing a string in result.Value), then with FindResults() to get typed values. If FindResults fails on line 65-68, result.Value retains the string from line 58 while result.Error is set, leading to potentially confusing state.

Consider restructuring to use only FindResults for value extraction:

🔎 Proposed refactor
-	// Execute the query to check if we get any result
-	var buf bytes.Buffer
-	if err := jp.Execute(&buf, data); err != nil {
-		result.Error = fmt.Errorf("failed to execute JSONPath: %w", err)
-		return result, nil
-	}
-
-	result.Value = strings.TrimSpace(buf.String())
-	if result.Value == "" {
-		return result, nil
-	}
-
-	// For complex queries that might return multiple values, use FindResults
+	// Use FindResults to get typed values
 	results, err := jp.FindResults(data)
 	if err != nil {
-		result.Error = fmt.Errorf("failed to find results for '%s': %w", originalField, err)
+		result.Error = fmt.Errorf("failed to extract field '%s': %w", originalField, err)
 		return result, nil
 	}
internal/criteria/types.go (1)

115-126: Consider removing duplicate method.

GetData() (lines 117-126) is functionally identical to the existing Data() method (lines 210-218). Both return a shallow copy of the internal data map. This duplication could lead to maintenance burden.

🔎 Proposed fix

Either remove GetData() and use Data() consistently, or have one delegate to the other:

 // GetData returns a shallow copy of the context data for direct field extraction.
 // This method is safe for concurrent use.
 func (c *EvaluationContext) GetData() map[string]interface{} {
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-	// Return shallow copy to prevent external modification
-	result := make(map[string]interface{}, len(c.data))
-	for k, v := range c.data {
-		result[k] = v
-	}
-	return result
+	return c.Data()
 }
internal/criteria/cel_evaluator_test.go (2)

164-165: Redundant assertions.

Lines 164 and 165 assert the same condition with opposite boolean checks:

  • assert.True(t, !result.HasError())
  • assert.False(t, result.HasError())

Consider keeping only one assertion for clarity.

🔎 Proposed fix
-		assert.True(t, !result.HasError())
 		assert.False(t, result.HasError())

175-175: Confusing double-negative assertion.

assert.False(t, !result.HasError()) is equivalent to assert.True(t, result.HasError()). The latter is more readable, especially since line 174 already correctly uses assert.True(t, result.HasError()).

🔎 Proposed fix
-		assert.False(t, !result.HasError())
+		// Line 174 already asserts HasError() is true; this line is redundant
internal/criteria/README.md (1)

137-137: Format the bare URL as a Markdown link.

Static analysis flagged a bare URL. Consider formatting it as a proper Markdown link for consistency.

🔎 Suggested fix
-See: https://kubernetes.io/docs/reference/kubectl/jsonpath/
+See: [Kubernetes JSONPath Reference](https://kubernetes.io/docs/reference/kubectl/jsonpath/)
internal/criteria/evaluator.go (1)

175-226: Consider handling whitespace-only field inputs.

The ExtractValue method trims the expression (line 199) but doesn't trim the field parameter before checking if it's empty. If field is whitespace-only (e.g., " "), it will be passed to EvaluateField, which delegates to ExtractField. Looking at ExtractField in the snippet (line 27), it does trim the field, so this is handled downstream. However, for consistency and to match the expression handling, consider trimming field here as well.

Also, the error message on line 225 could be clearer - "neither field nor expression defined" might confuse users if they passed whitespace strings.

🔎 Optional: Trim field for consistency
 func (e *Evaluator) ExtractValue(field, expression string) (*ExtractValueResult, error) {
 	result := &ExtractValueResult{}
+	field = strings.TrimSpace(field)
+	expression = strings.TrimSpace(expression)
 	if expression != "" {
 		// CEL expression mode
-		celResult, err := e.EvaluateCEL(strings.TrimSpace(expression))
+		celResult, err := e.EvaluateCEL(expression)
 		if err != nil {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50a640c and 3772d7d.

📒 Files selected for processing (19)
  • configs/adapter-config-template.yaml
  • internal/config_loader/README.md
  • internal/config_loader/types.go
  • internal/config_loader/validator.go
  • internal/config_loader/validator_test.go
  • internal/criteria/README.md
  • internal/criteria/cel_evaluator.go
  • internal/criteria/cel_evaluator_test.go
  • internal/criteria/evaluator.go
  • internal/criteria/evaluator_scenarios_test.go
  • internal/criteria/evaluator_test.go
  • internal/criteria/evaluator_version_test.go
  • internal/criteria/field_evaluator.go
  • internal/criteria/types.go
  • internal/executor/README.md
  • internal/executor/post_action_executor.go
  • internal/executor/post_action_executor_test.go
  • internal/executor/precondition_executor.go
  • test/integration/config-loader/config_criteria_integration_test.go
💤 Files with no reviewable changes (1)
  • internal/executor/post_action_executor_test.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: xueli181114
Repo: openshift-hyperfleet/hyperfleet-adapter PR: 13
File: internal/executor/post_action_executor.go:198-205
Timestamp: 2025-12-04T14:06:51.656Z
Learning: Logger refactor is tracked in HYPERFLEET-304 for the hyperfleet-adapter repository, which will address how CEL evaluation failures and similar errors are handled and logged.
📚 Learning: 2025-12-11T02:06:33.975Z
Learnt from: xueli181114
Repo: openshift-hyperfleet/hyperfleet-adapter PR: 17
File: internal/config_loader/validator_schema.go:126-133
Timestamp: 2025-12-11T02:06:33.975Z
Learning: In the hyperfleet-adapter repository (Go), environment variable validation for required parameters (those with `env.*` source, `Required=true`, and no default) occurs at config load time in `internal/config_loader/validator_schema.go`. This is intentional design - environment variables must be set before deployment, and the validation ensures fail-fast behavior with clear error messages.

Applied to files:

  • internal/config_loader/validator.go
  • internal/config_loader/validator_test.go
🧬 Code graph analysis (9)
internal/criteria/types.go (1)
internal/criteria/field_evaluator.go (1)
  • ExtractField (24-91)
internal/executor/post_action_executor.go (1)
internal/config_loader/types.go (1)
  • ParseValueDef (33-56)
internal/executor/precondition_executor.go (3)
internal/criteria/types.go (1)
  • NewEvaluationContext (75-80)
internal/criteria/evaluator.go (1)
  • NewEvaluator (55-70)
internal/criteria/cel_evaluator.go (1)
  • CELResult (26-40)
internal/config_loader/validator_test.go (4)
internal/config_loader/types.go (13)
  • AdapterConfig (59-64)
  • Metadata (67-71)
  • AdapterConfigSpec (74-82)
  • AdapterInfo (85-87)
  • HyperfleetAPIConfig (90-95)
  • KubernetesConfig (98-100)
  • Condition (189-193)
  • Precondition (151-158)
  • Parameter (104-111)
  • APICall (161-169)
  • Resource (229-236)
  • DiscoveryConfig (239-243)
  • CaptureField (182-186)
internal/criteria/types.go (1)
  • Operator (10-10)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/config_loader/loader.go (1)
  • Parse (105-121)
internal/criteria/cel_evaluator.go (1)
pkg/errors/cel_error.go (1)
  • NewCELEvalError (67-77)
internal/criteria/evaluator_scenarios_test.go (1)
internal/criteria/types.go (1)
  • OperatorGreaterThan (24-24)
internal/criteria/evaluator.go (2)
internal/criteria/field_evaluator.go (2)
  • FieldResult (11-14)
  • ExtractField (24-91)
internal/criteria/types.go (1)
  • Operator (10-10)
internal/criteria/evaluator_test.go (3)
internal/criteria/field_evaluator.go (1)
  • ExtractField (24-91)
internal/criteria/types.go (3)
  • OperatorEquals (14-14)
  • OperatorGreaterThan (24-24)
  • OperatorIn (18-18)
internal/config_loader/constants.go (1)
  • FieldValue (72-72)
test/integration/config-loader/config_criteria_integration_test.go (1)
internal/criteria/types.go (2)
  • OperatorExists (28-28)
  • OperatorGreaterThan (24-24)
🪛 markdownlint-cli2 (0.18.1)
internal/criteria/README.md

137-137: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (33)
configs/adapter-config-template.yaml (1)

115-135: LGTM!

The documentation for capture modes is clear and accurately reflects the new mutual exclusivity constraint between field and expression. The examples demonstrate both JSONPath and CEL usage patterns appropriately.

internal/criteria/cel_evaluator.go (1)

23-40: LGTM!

The simplified CELResult structure with Error instead of ErrorReason is cleaner. The HasError() method provides a clear API for checking error state, and the updated comments accurately document the behavior.

internal/config_loader/README.md (1)

95-118: LGTM!

The documentation for CaptureField is comprehensive, with clear examples showing simple dot notation, JSONPath, and CEL expression modes. The mutual exclusivity constraint is well-documented.

internal/criteria/evaluator_version_test.go (1)

26-27: LGTM!

The test assertions correctly use HasError() to check for evaluation success, aligning with the simplified CELResult API that removed IsSuccess().

internal/criteria/types.go (1)

130-144: LGTM!

The refactored GetNestedField correctly delegates to ExtractField with proper error handling. The nil check on line 137 is defensive but harmless.

internal/executor/post_action_executor.go (1)

162-177: LGTM!

The value definition handling is clean and well-structured:

  • Properly uses ParseValueDef to detect value definitions
  • Correctly handles extraction errors as parse errors (fail-fast)
  • Implements default value fallback with appropriate debug logging
internal/criteria/evaluator_scenarios_test.go (1)

1-373: LGTM! Test assertions correctly updated to use structured results.

All tests have been properly updated to access result.Matched instead of raw boolean values, aligning with the new EvaluationResult return type from EvaluateCondition and EvaluateConditions. The test coverage is comprehensive and the changes are consistent throughout.

internal/config_loader/validator.go (1)

176-211: LGTM! Well-structured capture field validation.

The validation logic correctly enforces:

  1. Name is required for each capture
  2. Mutual exclusivity between field and expression (exactly one must be set)
  3. CEL expression syntax validation when an expression is provided

The implementation follows existing validation patterns in the codebase.

internal/config_loader/validator_test.go (2)

11-24: Good refactoring to programmatic config construction.

The baseConfig() helper provides a clean, minimal valid configuration that tests can modify for specific scenarios. This approach is more maintainable than large YAML blocks and enables type-safe test setup.


534-579: Comprehensive capture field validation tests.

The tests properly cover all validation scenarios:

  • Valid capture with field only
  • Valid capture with expression only
  • Invalid: both field and expression set
  • Invalid: neither field nor expression set
  • Invalid: missing capture name
internal/criteria/README.md (1)

101-176: Well-documented JSONPath and value extraction APIs.

The new documentation sections clearly explain:

  • ExtractField for JSONPath extraction with supported syntax table
  • ExtractValue for unified field/expression extraction
  • Error handling semantics (parse errors vs. missing fields)
  • FieldResult and ExtractValueResult structures

This will help developers understand the new extraction capabilities.

internal/config_loader/types.go (2)

33-56: Consider whether silent error handling is intentional.

ParseValueDef returns (nil, false) for all error cases (marshal failure, unmarshal failure, not a map). This makes it impossible for callers to distinguish between "not a value definition" and "malformed value definition".

If malformed definitions should be caught during validation, this is fine since the caller will treat it as a literal value. However, if debugging is needed, consider logging or returning the error.

Is this silent error handling intentional? If the expectation is that validation catches structural issues elsewhere, then this is acceptable. Otherwise, consider returning a third value or logging on parse failures.


177-186: LGTM! CaptureField now supports both extraction modes.

The updated CaptureField struct properly supports mutually exclusive Field (JSONPath) and Expression (CEL) modes with omitempty tags, aligning with the validator enforcement added in validator.go.

internal/executor/precondition_executor.go (2)

130-148: Evaluator creation failure silently skips all captures.

When NewEvaluator fails (line 130-132), a warning is logged but all captures are skipped without further indication. This could lead to missing data that downstream conditions or post-actions depend on.

Consider whether this should be an error that aborts the precondition, or at minimum ensure the warning message clearly indicates that all captures were skipped.

Is silently skipping all captures on evaluator creation failure the intended behavior? If captures are required for subsequent conditions, this could cause confusing failures later.


135-146: Good error handling pattern for value extraction.

The distinction between parse errors (return immediately) and field-missing errors (warn and continue) is appropriate:

  • Parse errors indicate configuration issues that should fail the operation
  • Missing fields may be intentional and can use defaults

The debug logging of captured values with their source is helpful for troubleshooting.

internal/executor/README.md (2)

162-184: Clear documentation of capture modes.

The examples effectively demonstrate all three capture approaches:

  1. Simple dot notation (status.phase)
  2. JSONPath for complex extraction ({.items[?(@.adapter=='landing-zone-adapter')]...})
  3. CEL expression for computed values (items.filter(i, i.status == 'active').size())

The note about mutual exclusivity between field and expression is helpful.


283-287: Payload value types well documented.

The documentation clearly explains the three payload value patterns:

  • Direct string with Go template support
  • Field extraction with JSONPath and optional default
  • CEL expression with optional default

This aligns with the ValueDef type implementation.

test/integration/config-loader/config_criteria_integration_test.go (3)

179-182: Appropriate handling for missing field edge case.

The assertion assert.True(t, err != nil || !result.Matched, ...) correctly handles the case where the field doesn't exist - the behavior could be either an error or a non-match depending on implementation. This defensive assertion is appropriate for integration tests that shouldn't over-specify internal behavior.


268-298: LGTM! ExtractValue and EvaluateCondition usage updated correctly.

The tests properly:

  1. Call ExtractValue and access result.Value for extracted data
  2. Handle errors from extraction appropriately
  3. Use EvaluateCondition with structured result access

331-358: Good null safety test coverage.

The tests verify proper error handling when:

  • A resource path leads to a nil value (clusterController: nil)
  • A deeply nested path has a null intermediate (status: nil)

Both cases correctly expect errors from ExtractValue, which aligns with the documented behavior that missing fields return errors rather than nil values.

internal/criteria/evaluator_test.go (10)

600-600: LGTM!

The assertion correctly accesses the new result.Matched field from the structured EvaluationResult return type, aligning with the API refactor.


652-652: LGTM!

Consistent with the API change to return *ConditionsResult with a Matched field.


658-722: LGTM!

The TestExtractField function properly tests the new ExtractField API with various path scenarios. The error handling pattern on lines 711-713 correctly accounts for errors being returned either as the function error or captured in result.Error.


724-815: Comprehensive JSONPath extraction tests.

Good coverage of JSONPath features including:

  • Wildcard extraction ({.items[*].adapter})
  • Index access ({.items[0].adapter})
  • Filter expressions ({.items[?(@.adapter=='landing-zone-adapter')]...})
  • Auto-wrapping of paths without braces

This validates the new JSONPath capabilities well.


817-843: LGTM!

Good convenience function tests covering simple extraction, filter expressions, and wildcard extraction patterns.


917-945: LGTM!

The TestExtractValue correctly validates the unified extraction interface. The test on lines 941-944 appropriately verifies that non-existent fields return nil value without an error, allowing callers to apply defaults.


947-982: LGTM!

Thorough tests for EvaluateCondition covering multiple operators and verifying the structured result fields (Matched, FieldValue, Field, Operator, ExpectedValue).


984-1028: LGTM!

Good coverage of EvaluateConditions including verification of:

  • FailedCondition index tracking
  • ExtractedFields map population even when conditions fail
  • Multiple condition evaluation flow

1064-1081: LGTM!

Null handling tests correctly verify the new behavior where accessing fields on null parents returns nil value (not error), enabling default value fallback.


1095-1103: LGTM!

Deep null path test validates that deeply nested null paths return nil value without parse errors, consistent with the new extraction semantics.

internal/criteria/evaluator.go (3)

95-106: LGTM!

Clean delegation to ExtractField with good documentation covering both simple dot notation and JSONPath syntax examples.


108-146: LGTM!

The EvaluateCondition method properly structures the evaluation flow:

  1. Retrieves field value with error wrapping
  2. Builds result with all context fields populated
  3. Handles OperatorExists specially (no expected value)
  4. Uses the operator function map for other operators

Good error handling with the custom EvaluationError type.


148-173: LGTM!

The EvaluateConditions correctly:

  • Initializes FailedCondition to -1 (no failure)
  • Records the first failed condition index
  • Populates ExtractedFields regardless of match result
  • Short-circuits the Matched flag but continues evaluating all conditions

Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/criteria/evaluator_test.go (1)

917-945: Add test coverage for missing ExtractValue scenarios.

The TestExtractValue test only covers field extraction mode. Missing test cases for:

  1. CEL expression mode: Test ExtractValue("", "expression") path
  2. Mutual exclusivity: Test behavior when both field and expression are provided (should this error?)
  3. Neither parameter: Test ExtractValue("", "") returns expected error

These gaps should be addressed, especially test case #2 which relates to the mutual exclusivity validation issue flagged in evaluator.go.

🔎 Suggested additional test cases
// Test CEL expression mode
result, err := evaluator.ExtractValue("", "cluster.metadata.name == 'test-cluster'")
require.NoError(t, err)
assert.NotNil(t, result.Value)

// Test neither field nor expression - should error
result, err = evaluator.ExtractValue("", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "neither field nor expression")

// Test both field and expression - should error (if validation added)
result, err = evaluator.ExtractValue("cluster.metadata.name", "cluster.metadata.name == 'test'")
assert.Error(t, err)
assert.Contains(t, err.Error(), "mutually exclusive")
🧹 Nitpick comments (1)
internal/criteria/cel_evaluator_test.go (1)

201-201: Assertion pattern is valid but slightly verbose.

Using !result.HasError() works correctly. Consider using assert.False(t, result.HasError()) for slightly clearer intent, though the current form is functionally equivalent.

Also applies to: 209-209

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3772d7d and a5dd796.

📒 Files selected for processing (19)
  • configs/adapter-config-template.yaml
  • internal/config_loader/README.md
  • internal/config_loader/types.go
  • internal/config_loader/validator.go
  • internal/config_loader/validator_test.go
  • internal/criteria/README.md
  • internal/criteria/cel_evaluator.go
  • internal/criteria/cel_evaluator_test.go
  • internal/criteria/evaluator.go
  • internal/criteria/evaluator_scenarios_test.go
  • internal/criteria/evaluator_test.go
  • internal/criteria/evaluator_version_test.go
  • internal/criteria/field_evaluator.go
  • internal/criteria/types.go
  • internal/executor/README.md
  • internal/executor/post_action_executor.go
  • internal/executor/post_action_executor_test.go
  • internal/executor/precondition_executor.go
  • test/integration/config-loader/config_criteria_integration_test.go
💤 Files with no reviewable changes (1)
  • internal/executor/post_action_executor_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • internal/config_loader/README.md
  • internal/config_loader/types.go
  • internal/criteria/README.md
  • internal/criteria/field_evaluator.go
  • internal/criteria/evaluator_scenarios_test.go
  • configs/adapter-config-template.yaml
  • internal/config_loader/validator_test.go
  • internal/criteria/evaluator_version_test.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: xueli181114
Repo: openshift-hyperfleet/hyperfleet-adapter PR: 13
File: internal/executor/post_action_executor.go:198-205
Timestamp: 2025-12-04T14:06:51.656Z
Learning: Logger refactor is tracked in HYPERFLEET-304 for the hyperfleet-adapter repository, which will address how CEL evaluation failures and similar errors are handled and logged.
📚 Learning: 2025-12-11T02:06:33.975Z
Learnt from: xueli181114
Repo: openshift-hyperfleet/hyperfleet-adapter PR: 17
File: internal/config_loader/validator_schema.go:126-133
Timestamp: 2025-12-11T02:06:33.975Z
Learning: In the hyperfleet-adapter repository (Go), environment variable validation for required parameters (those with `env.*` source, `Required=true`, and no default) occurs at config load time in `internal/config_loader/validator_schema.go`. This is intentional design - environment variables must be set before deployment, and the validation ensures fail-fast behavior with clear error messages.

Applied to files:

  • internal/config_loader/validator.go
🧬 Code graph analysis (7)
internal/config_loader/validator.go (2)
internal/config_loader/constants.go (4)
  • FieldSpec (9-9)
  • FieldPreconditions (19-19)
  • FieldCapture (49-49)
  • FieldExpression (51-51)
internal/config_loader/types.go (1)
  • CaptureField (182-186)
internal/executor/post_action_executor.go (1)
internal/config_loader/types.go (1)
  • ParseValueDef (33-56)
internal/criteria/evaluator_test.go (3)
internal/criteria/field_evaluator.go (1)
  • ExtractField (24-93)
internal/criteria/types.go (3)
  • OperatorEquals (14-14)
  • OperatorGreaterThan (24-24)
  • OperatorIn (18-18)
internal/config_loader/constants.go (1)
  • FieldValue (72-72)
test/integration/config-loader/config_criteria_integration_test.go (1)
internal/criteria/types.go (2)
  • OperatorExists (28-28)
  • OperatorGreaterThan (24-24)
internal/executor/precondition_executor.go (3)
internal/criteria/types.go (1)
  • NewEvaluationContext (75-80)
internal/criteria/evaluator.go (1)
  • NewEvaluator (55-70)
internal/criteria/cel_evaluator.go (1)
  • CELResult (26-40)
internal/criteria/evaluator.go (2)
internal/criteria/field_evaluator.go (2)
  • FieldResult (11-14)
  • ExtractField (24-93)
internal/criteria/types.go (1)
  • Operator (10-10)
internal/criteria/types.go (1)
internal/criteria/field_evaluator.go (1)
  • ExtractField (24-93)
🔇 Additional comments (23)
internal/executor/README.md (2)

162-184: Documentation updates look accurate and helpful.

The capture modes documentation clearly explains the mutually exclusive field vs expression options, with good examples showing simple dot notation, JSONPath, and CEL expression usage. This aligns well with the validation logic added in validator.go.


283-287: Payload value types documentation is clear.

The three value types (direct string, field extraction with default, CEL expression with default) are well-documented and match the ParseValueDef implementation in config_loader/types.go.

internal/criteria/types.go (1)

120-131: Clean delegation to new ExtractField API.

The refactored implementation properly handles all error cases:

  1. Function-level errors from ExtractField
  2. Nil result handling
  3. Result-level errors via result.Error

This maintains backward compatibility while leveraging the new extraction infrastructure.

internal/criteria/cel_evaluator_test.go (1)

176-176: Test assertions correctly updated for new error representation.

The assertions now use result.Error.Error() to access error messages, aligning with the simplified CELResult struct that removed the ErrorReason field in favor of the single Error field.

Also applies to: 194-194

internal/executor/post_action_executor.go (1)

162-177: Value definition handling is well-structured.

The new flow correctly:

  1. Detects value definitions via ParseValueDef
  2. Uses ExtractValue for unified field/expression extraction
  3. Fails fast on parse errors (configuration bugs)
  4. Falls back to default when extraction yields nil

One consideration: the debug log at line 172-173 logs the default value. Ensure sensitive defaults aren't used in production configs, though debug-level logging is typically acceptable.

internal/config_loader/validator.go (1)

180-211: Capture field validation is comprehensive and well-implemented.

The validation correctly enforces:

  1. Required name field
  2. Mutual exclusivity of field and expression (exactly one must be set)
  3. CEL syntax validation when expression is provided

The error messages with path context will help users quickly identify configuration issues.

internal/executor/precondition_executor.go (3)

130-148: Capture evaluation flow looks correct, but consider evaluator creation failure impact.

When captureEvaluator creation fails (line 131-132), all captures are silently skipped with only a warning log. This could mask configuration issues. Consider whether this should be a harder failure, or at minimum ensure the warning is prominent enough for operators to notice.

The individual capture handling is well-designed:

  • Parse errors (line 136-138) cause immediate failure (correct - configuration bug)
  • Missing fields (line 140-143) log a warning and continue (correct - valid runtime scenario)

171-171: API migration to EvaluateConditions looks correct.

The switch from EvaluateConditionsWithResult to EvaluateConditions aligns with the broader API refactoring in this PR.


238-240: CEL error formatting correctly uses Error field.

The formatting now uses result.CELResult.Error directly with %v, which aligns with the removal of the separate ErrorReason field from CELResult.

internal/criteria/cel_evaluator.go (2)

24-40: CELResult struct simplification is a good cleanup.

Removing the redundant ErrorReason field in favor of the single Error field simplifies the API. Callers can use HasError() to check for errors and Error.Error() to get the message when needed.


160-166: Error result construction is consistent with simplified struct.

The evaluation error result now correctly initializes only the necessary fields. The explicit nil for Value and false for Matched make the error state clear.

test/integration/config-loader/config_criteria_integration_test.go (4)

85-89: Integration tests correctly updated for new evaluator API.

The migration from EvaluateConditionWithResult/EvaluateConditionsWithResult to EvaluateCondition/EvaluateConditions is consistent throughout the test file.

Also applies to: 111-111


268-276: ExtractValue usage correctly accesses result.Value.

The test properly uses the new ExtractValue API and accesses the extracted value via result.Value. This aligns with the ExtractValueResult struct design.


179-182: Flexible assertion handles both error and non-match cases.

The assertion err != nil || !result.Matched correctly handles the case where a missing field could either return an error or a non-matched result, depending on the implementation path. This is appropriate for testing the exists operator on a missing field.


331-343: Null safety tests correctly verify ExtractValue semantics.

The tests verify that ExtractValue returns a nil value (not an error) for null paths, which allows callers to apply defaults. The condition evaluation test at line 337-342 properly checks that null paths fail evaluation.

internal/criteria/evaluator.go (4)

95-106: LGTM!

The EvaluateField method is well-documented with clear examples of both simple dot notation and full JSONPath syntax. The delegation to ExtractField is clean and maintains separation of concerns.


108-173: LGTM!

The refactored EvaluateCondition and EvaluateConditions methods properly aggregate evaluation results and extract field values. The new signature for EvaluateCondition taking individual parameters (field, operator, value) improves API clarity.


175-180: LGTM!

The ExtractValueResult structure provides a clear contract for extraction outcomes, bundling the value, source identifier, and any errors encountered.


215-220: Verify the field extraction error semantics are clearly understood.

The ExtractValue method has nuanced error handling: parse errors (invalid JSONPath syntax) return an error, but field-not-found conditions return a result with nil Value and no error. This allows callers to apply default values when fields are missing.

While this design is documented in comments and tested, it differs from typical Go error handling patterns where "not found" might also be an error. Ensure this behavior is clearly communicated in public documentation and that callers understand when to check result.Value == nil versus err != nil.

Are there user-facing docs or examples that explain when ExtractValue returns an error versus a nil Value? This distinction is critical for proper default value handling.

internal/criteria/evaluator_test.go (4)

600-600: LGTM!

Test correctly updated to access result.Matched from the new EvaluationResult structure.


658-843: Excellent test coverage for field extraction!

The new tests comprehensively cover:

  • Simple and nested field extraction
  • Full JSONPath syntax including wildcards and filters
  • Array handling with single vs multiple results
  • Error handling for invalid/missing fields

The dual error checking pattern (err != nil || result.Error != nil) correctly handles both parse errors and extraction errors.


947-1028: LGTM!

The tests correctly validate the refactored EvaluateCondition and EvaluateConditions methods, including proper verification of:

  • Individual condition matching
  • Extracted field values
  • Failed condition tracking
  • Multiple condition aggregation

1046-1104: LGTM!

The null handling tests properly validate the extraction semantics where:

  • Accessing fields on null parents returns nil Value without error (allowing defaults)
  • Existing fields still extract correctly
  • Deep null paths are handled gracefully

These tests confirm the error handling design discussed in the ExtractValue implementation.

…cel expression.

In this commit I
- Removed useless code and the testing code
- Refatored evaluator to support cel expression and jsonPATH field
- Supported default value when field and cel expression match failure
- Supported expression and complex field in capture
Copy link

@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 (3)
internal/criteria/field_evaluator.go (1)

42-56: Clarify "backward compatible" comment.

Line 43 mentions "backward compatible," but since this is a new file introducing ExtractField, the comment is misleading. Consider revising to: "AllowMissingKeys(false) ensures missing fields are treated as errors during validation."

🔎 Suggested comment revision
-	// AllowMissingKeys(false) ensures we get errors for missing fields (backward compatible)
+	// AllowMissingKeys(false) ensures missing fields produce errors during validation
internal/config_loader/types.go (1)

30-56: Consider adding mutual exclusivity validation.

While ParseValueDef validates that at least one of field or expression is set, it doesn't enforce that both cannot be set simultaneously. This could allow invalid configurations to pass parsing, even though they're documented as mutually exclusive.

🔎 Proposed fix to add mutual exclusivity check
 	// Must have at least one of field or expression
 	if valueDef.Field == "" && valueDef.Expression == "" {
 		return nil, false
 	}
+	
+	// Enforce mutual exclusivity
+	if valueDef.Field != "" && valueDef.Expression != "" {
+		return nil, false
+	}
 
 	return &valueDef, true

Note: If the config loader validator already enforces this constraint at configuration load time, this additional check provides defense-in-depth for runtime usage outside the config loader.

internal/executor/README.md (1)

193-211: Minor: Consider rewording to remove word repetition.

The documentation is excellent and comprehensive. However, static analysis detected a minor stylistic issue on line 199 where "only" appears twice: "API Response only | Only the parsed JSON response". Consider rephrasing for better readability.

🔎 Suggested rewording
-| **Capture** (`field`/`expression`) | API Response only | Only the parsed JSON response (e.g., `status.phase`, `items[0].name`) |
+| **Capture** (`field`/`expression`) | API Response data | The parsed JSON response (e.g., `status.phase`, `items[0].name`) |
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5dd796 and 74b71dd.

📒 Files selected for processing (19)
  • configs/adapter-config-template.yaml
  • internal/config_loader/README.md
  • internal/config_loader/types.go
  • internal/config_loader/validator.go
  • internal/config_loader/validator_test.go
  • internal/criteria/README.md
  • internal/criteria/cel_evaluator.go
  • internal/criteria/cel_evaluator_test.go
  • internal/criteria/evaluator.go
  • internal/criteria/evaluator_scenarios_test.go
  • internal/criteria/evaluator_test.go
  • internal/criteria/evaluator_version_test.go
  • internal/criteria/field_evaluator.go
  • internal/criteria/types.go
  • internal/executor/README.md
  • internal/executor/post_action_executor.go
  • internal/executor/post_action_executor_test.go
  • internal/executor/precondition_executor.go
  • test/integration/config-loader/config_criteria_integration_test.go
💤 Files with no reviewable changes (1)
  • internal/executor/post_action_executor_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/config_loader/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/criteria/evaluator_version_test.go
  • internal/config_loader/validator.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: xueli181114
Repo: openshift-hyperfleet/hyperfleet-adapter PR: 13
File: internal/executor/post_action_executor.go:198-205
Timestamp: 2025-12-04T14:06:51.656Z
Learning: Logger refactor is tracked in HYPERFLEET-304 for the hyperfleet-adapter repository, which will address how CEL evaluation failures and similar errors are handled and logged.
📚 Learning: 2025-12-11T02:06:33.975Z
Learnt from: xueli181114
Repo: openshift-hyperfleet/hyperfleet-adapter PR: 17
File: internal/config_loader/validator_schema.go:126-133
Timestamp: 2025-12-11T02:06:33.975Z
Learning: In the hyperfleet-adapter repository (Go), environment variable validation for required parameters (those with `env.*` source, `Required=true`, and no default) occurs at config load time in `internal/config_loader/validator_schema.go`. This is intentional design - environment variables must be set before deployment, and the validation ensures fail-fast behavior with clear error messages.

Applied to files:

  • internal/config_loader/validator_test.go
  • internal/criteria/evaluator.go
🧬 Code graph analysis (9)
internal/executor/post_action_executor.go (1)
internal/config_loader/types.go (1)
  • ParseValueDef (33-56)
internal/executor/precondition_executor.go (3)
internal/criteria/types.go (1)
  • NewEvaluationContext (75-80)
internal/criteria/evaluator.go (1)
  • NewEvaluator (55-70)
internal/criteria/cel_evaluator.go (1)
  • CELResult (22-36)
internal/config_loader/validator_test.go (4)
internal/config_loader/types.go (13)
  • AdapterConfig (59-64)
  • Metadata (67-71)
  • AdapterConfigSpec (74-82)
  • AdapterInfo (85-87)
  • HyperfleetAPIConfig (90-95)
  • KubernetesConfig (98-100)
  • Condition (189-193)
  • Precondition (151-158)
  • Parameter (104-111)
  • APICall (161-169)
  • Resource (229-236)
  • DiscoveryConfig (239-243)
  • CaptureField (182-186)
internal/criteria/types.go (1)
  • Operator (10-10)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/config_loader/loader.go (1)
  • Parse (105-121)
internal/criteria/field_evaluator.go (2)
pkg/errors/error.go (1)
  • New (107-123)
internal/config_loader/loader.go (1)
  • Parse (105-121)
test/integration/config-loader/config_criteria_integration_test.go (1)
internal/criteria/types.go (2)
  • OperatorExists (28-28)
  • OperatorGreaterThan (24-24)
internal/criteria/evaluator_test.go (1)
internal/criteria/field_evaluator.go (1)
  • ExtractField (24-93)
internal/criteria/types.go (1)
internal/criteria/field_evaluator.go (2)
  • FieldResult (11-14)
  • ExtractField (24-93)
internal/criteria/evaluator_scenarios_test.go (3)
internal/criteria/types.go (3)
  • OperatorGreaterThan (24-24)
  • NewEvaluationContext (75-80)
  • OperatorContains (22-22)
internal/criteria/evaluator.go (1)
  • NewEvaluator (55-70)
pkg/logger/test_support.go (1)
  • NewTestLogger (17-32)
internal/criteria/evaluator.go (3)
internal/criteria/field_evaluator.go (2)
  • FieldResult (11-14)
  • ExtractField (24-93)
internal/criteria/types.go (1)
  • Operator (10-10)
internal/config_loader/constants.go (1)
  • FieldValue (72-72)
🪛 LanguageTool
internal/executor/README.md

[style] ~199-~199: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...ld/expression) | API Response only | Only the parsed JSON response (e.g., status...

(ADVERB_REPETITION_PREMIUM)

🔇 Additional comments (31)
internal/criteria/types.go (1)

115-121: LGTM! Clean delegation to the new extraction API.

The rename from GetNestedField to GetField and the delegation to ExtractField align well with the broader API migration to structured result types.

internal/config_loader/validator_test.go (2)

11-24: LGTM! Clean test helper for baseline configuration.

The baseConfig() helper provides a minimal valid configuration that tests can mutate for specific scenarios, making the test setup clearer and more maintainable than YAML-based approaches.


534-579: Excellent capture field validation coverage.

These tests thoroughly validate the mutual exclusivity of field and expression, ensuring that capture definitions follow the expected structure. The test cases cover all edge cases: field-only, expression-only, both set (invalid), neither set (invalid), and missing name.

internal/criteria/cel_evaluator_test.go (2)

12-20: LGTM! Constructor signature simplified.

The removal of the context and logger parameters from newCELEvaluator simplifies the API and aligns with the refactored evaluator architecture.


169-177: LGTM! Error handling aligned with CELResult structure.

The change from result.ErrorReason to result.Error.Error() correctly accesses the error through the Error field, consistent with the refactored CELResult type.

internal/criteria/field_evaluator.go (2)

33-40: Auto-conversion enhances usability.

The automatic conversion from simple dot notation (e.g., metadata.name) to JSONPath format (e.g., {.metadata.name}) improves user experience by accepting both formats. This aligns with the decision from past review discussions to maintain flexibility.


52-70: Double execution pattern is intentional and necessary.

The code executes the JSONPath twice: first with Execute to validate the path and check for empty results, then with FindResults to retrieve typed values. This pattern is necessary because:

  • Execute validates the path exists and produces output (string representation)
  • FindResults retrieves the actual typed values (not just strings)

While this may seem redundant, it's the correct approach for distinguishing between "field not found" and "field exists but empty."

internal/criteria/cel_evaluator.go (3)

19-36: LGTM! Simplified CELResult structure.

The removal of ErrorReason and IsSuccess() simplifies the API. Error state is now determined solely by checking Error != nil via HasError(), which is cleaner and more idiomatic.


43-58: LGTM! Streamlined constructor signature.

Removing the context and logger parameters from newCELEvaluator simplifies the API and removes unnecessary dependencies. The evaluator now focuses solely on CEL evaluation with the provided evaluation context.


148-160: LGTM! Clean error handling in EvaluateSafe.

The error handling is well-structured:

  • Parse errors are returned as errors (fail fast for configuration bugs)
  • Evaluation errors are captured in CELResult.Error (safe handling for missing data)

The comment at line 153 correctly notes that callers should handle logging based on the result, providing flexibility for different logging strategies.

internal/criteria/README.md (1)

101-176: Excellent documentation for the new extraction APIs.

The documentation clearly explains:

  • JSONPath support with both simple dot notation and full JSONPath syntax
  • ExtractField and ExtractValue usage with comprehensive examples
  • Error handling distinction between parse errors (returned) and missing fields (nil value)
  • Supported JSONPath syntax with a helpful reference table

This will greatly assist developers using the new APIs.

internal/executor/post_action_executor.go (1)

159-203: LGTM! Well-structured value processing with default support.

The refactored processValue method cleanly handles value definitions:

  1. Detects value definitions via ParseValueDef
  2. Extracts values using the unified ExtractValue API
  3. Fails fast on parse errors (line 166-168)
  4. Falls back to defaults when extraction yields nil (lines 170-174)

The error handling strategy is appropriate: parse errors indicate configuration bugs and should fail immediately, while missing values gracefully use defaults.

internal/criteria/evaluator_scenarios_test.go (2)

259-345: Excellent edge case coverage for the contains operator.

The new TestContainsOperatorEdgeCases test thoroughly validates:

  • String substring matching (found/not found)
  • Slice element containment with various types
  • Integer slice handling
  • Error cases for unsupported types (int field)
  • Nil and missing field error handling

This comprehensive test coverage will help catch regressions in contains operator behavior.


427-460: LGTM! Node count validation uses structured results correctly.

The test correctly combines result1.Matched && result2.Matched to validate range conditions, which is more explicit and maintainable than relying on raw boolean logic. The test coverage includes boundary cases (minimum, maximum, below/above range).

configs/adapter-config-template.yaml (2)

101-170: LGTM! Excellent documentation improvements.

The expanded documentation clearly distinguishes between capture scope (API response only) and conditions scope (full execution context). The examples for both field-based and CEL-based captures are helpful and comprehensive.


265-265: LGTM! Simplified timestamp field.

The change from a nested value structure to a direct Go template string is appropriate for timestamp fields, which are rendered at template evaluation time rather than extracted from the execution context.

internal/config_loader/types.go (2)

177-186: LGTM! Well-documented capture field modes.

The updated CaptureField struct properly supports both JSONPath and CEL modes with clear documentation. The omitempty tags ensure clean YAML serialization.


3-7: LGTM! Necessary import addition.

The gopkg.in/yaml.v3 import is required for the ParseValueDef implementation and is a standard, well-maintained library.

internal/executor/precondition_executor.go (3)

121-123: LGTM! Enables full API response access in conditions.

Storing the complete API response under the precondition name allows conditions to access any part of the response (e.g., checkClusterStatus.status.phase), which is a valuable feature documented in the configuration template.


128-153: LGTM! Robust capture logic with appropriate error handling.

The new capture implementation properly distinguishes between:

  • Parse errors (invalid field path or CEL expression) → fails fast
  • Extraction errors (field not found or CEL evaluation null) → logs warning and continues

This allows optional captures that may not always be present in API responses, while still catching configuration errors.


171-224: LGTM! Clean API migration to new evaluator interface.

The condition evaluation has been properly updated to use the new EvaluateConditions API. The result mapping and error handling are correct, and the execution context tracking is maintained.

internal/executor/README.md (1)

313-339: LGTM! Clear post-action payload examples.

The documentation clearly shows the three types of payload values (direct string, field extraction, CEL expression) with concrete examples. This aligns well with the ValueDef implementation.

test/integration/config-loader/config_criteria_integration_test.go (2)

76-94: LGTM! Tests properly migrated to new evaluator API.

The test correctly uses the new EvaluateCondition signature and accesses results through the structured result object. The assertions validate both matching status and field values.


267-360: LGTM! Comprehensive testing of extraction and null handling.

The tests correctly validate:

  • Field extraction via ExtractValue returning values through result.Value
  • Null path handling returning nil value without parse errors
  • Condition evaluation on null paths properly failing

The test coverage aligns with the documented behavior of the new extraction APIs.

internal/criteria/evaluator.go (4)

95-106: LGTM! Clean field extraction interface.

The new EvaluateField function provides a clear public API for JSONPath-based field extraction with comprehensive documentation and examples.


108-142: LGTM! Properly updated to use new field extraction API.

The EvaluateCondition function correctly uses the new GetField method and handles results through the structured FieldResult object. The operator evaluation logic remains robust.


191-233: LGTM! Well-implemented unified extraction with proper validation.

The ExtractValue function correctly implements:

  1. Mutual exclusivity validation (lines 196-198) - addresses the previous review concern
  2. Two extraction modes - CEL expression or JSONPath field
  3. Appropriate error handling - distinguishing parse errors (fast fail) from extraction errors (log + allow defaults)

The warning logs (lines 209, 225) provide visibility into extraction failures while allowing callers to use default values, which is the intended behavior for optional fields.


144-169: LGTM! Proper multi-condition evaluation with field extraction.

The EvaluateConditions function correctly:

  • Uses the new EvaluateCondition API
  • Aggregates individual results
  • Populates the ExtractedFields map for downstream use
  • Tracks the first failed condition index
internal/criteria/evaluator_test.go (3)

38-91: LGTM! Tests properly validate new GetField semantics.

The updated tests correctly validate that:

  • Parse errors are returned as errors
  • Missing fields return nil value without error
  • Valid fields return expected values

The wantNil flag provides clear test case documentation.


658-843: LGTM! Comprehensive ExtractField test coverage.

The test suite thoroughly validates:

  • Simple and nested field extraction
  • JSONPath syntax (wildcards, filters, array access)
  • Single vs. multiple result handling
  • Error conditions

The tests ensure the extraction logic works correctly for all supported use cases.


917-1104: LGTM! Excellent test coverage for value extraction and null handling.

The tests comprehensively validate:

  • Field-based and CEL-based extraction via ExtractValue
  • Graceful handling of missing fields (nil value, no error)
  • Proper behavior when conditions evaluate null paths
  • Deep null path traversal

This ensures the extraction APIs handle edge cases correctly and align with documented behavior.

@86254860
Copy link

/lgtm

@openshift-ci
Copy link

openshift-ci bot commented Dec 30, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: 86254860

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-bot openshift-merge-bot bot merged commit 2acf251 into openshift-hyperfleet:main Dec 30, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants