Skip to content

Conversation

@xueli181114
Copy link
Contributor

@xueli181114 xueli181114 commented Nov 27, 2025

Summary

Implement core adapter configuration, criteria evaluation, and API client components:

Config Loader: Complete YAML configuration parser with validation for adapter specs, parameters, preconditions, and resources
CEL Criteria Evaluator: Expression evaluation engine for preconditions and conditional logic
HyperFleet API Client: HTTP client with timeout, retry, and authentication for API interactions
Main Application: Integrate all components with command-line config loading and event processing framework

Test Coverage

• Unit tests for all config validation scenarios
• CEL expression evaluation test suite
• API client integration tests with containerized endpoints
• E2E configuration loading tests

Summary by CodeRabbit

  • New Features
    • Added config loader with schema/validation, CEL-based criteria evaluator, HyperFleet HTTP client, Kubernetes resource discovery, and templated deployment/job payloads for cluster status reporting.
  • Tests
    • Expanded unit and integration suites, shared test environments, longer timeouts, container test utilities and cleanup helpers.
  • Documentation
    • Added READMEs for config loader, criteria engine, and HyperFleet client.
  • Chores
    • Added imageTag param, renamed broker config keys, updated Makefile and integration targets.

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

@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Walkthrough

Adds a YAML adapter config loader with structural and semantic validation (internal/config_loader) including constants, types, accessors, file-reference loading, and extensive tests. Introduces a CEL-based criteria evaluator and CEL helper utilities (internal/criteria) with evaluation, conversion, and tests. Adds a configurable HyperFleet HTTP client with retries/backoff and request/response models (internal/hyperfleet_api) plus API error type helpers (pkg/errors). Adds Kubernetes discovery helpers (internal/k8s_client/discovery.go) and client improvements. Adds templates, Helm manifests, test fixtures, integration tests, shared envtest orchestration, and test utilities (container, shared container lifecycle). Wires config loading and HyperFleet API client creation into cmd/adapter startup and updates broker subscriber handler scaffolding to accept adapter config and api client.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Main as cmd/adapter Main
    participant Loader as config_loader.Load/Parse
    participant Schema as SchemaValidator
    participant Semantic as Validator
    participant API as hyperfleet_api.Client
    participant Subscriber as Broker Subscriber
    participant Handler as Event Handler
    participant Eval as criteria.Evaluator
    participant K8s as k8s_client

    Main->>Loader: Load(configPath, WithBaseDir...)
    Loader->>Schema: Parse & ValidateStructure
    Schema-->>Loader: config (manifests/refs loaded)
    Loader->>Semantic: Validate(config)
    Semantic-->>Loader: semantic result
    Loader-->>Main: AdapterConfig

    Main->>API: NewClient(WithConfig(adapter.HyperfleetAPIConfig)...)
    API-->>Main: apiClient

    Main->>Subscriber: NewSubscriber(...)->Subscribe(handler(adapterConfig, apiClient))
    Subscriber-->>Handler: onMessage(event)
    Handler->>Eval: Build EvaluationContext(event & captures)
    Handler->>Eval: Evaluate preconditions (operators + CEL)
    alt preconditions satisfied
        Handler->>API: perform apiCall(s) (capture/post-actions)
        API-->>Handler: responses
        Handler->>K8s: DiscoverResources (if configured)
        K8s-->>Handler: resources
        Handler->>Handler: build payloads/templates
        Handler->>API: POST report/status
        API-->>Handler: response
    else preconditions failed
        Handler-->>Subscriber: ack/skip
    end
    Subscriber-->>Main: shutdown/cleanup
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Files/areas needing extra attention:

  • internal/config_loader/: types.go, loader.go, validator_schema.go, validator.go, accessors.go, constants.go, loader/validator tests (complex validation, CEL integration, manifest ref loading)
  • internal/criteria/: types.go, evaluator.go, cel_evaluator.go, and tests (operator semantics, CEL conversion, nested field access)
  • internal/hyperfleet_api/: types.go, client.go, client_test.go (retry/backoff correctness, timeouts, request lifecycle)
  • pkg/errors/api_error.go (error construction, wrapping, helpers)
  • cmd/adapter/main.go and broker subscriber handler wiring (flag handling, config loading, api client creation, lifecycle)
  • internal/k8s_client/: discovery.go and client.go (DiscoverResources behavior, selector parsing)
  • test/integration/* and test utilities: shared envtest/TestMain, testutil container helpers, integration scripts (image/build/run) and updated integration tests

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.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 'feat: implement config loader, criteria evaluator, and API client' clearly and accurately summarizes the three main components added in this changeset, matching the PR objectives.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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: 5

Caution

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

⚠️ Outside diff range comments (1)
configs/broker-configmap-pubsub-template.yaml (1)

93-97: Update example deployment to use renamed keys.

The example deployment still references the old SUBSCRIPTION_ID key. Update it to use BROKER_SUBSCRIPTION_ID to match the renamed data keys on lines 24 and 28.

         # Adapter-specific configuration
-        - name: SUBSCRIPTION_ID
+        - name: BROKER_SUBSCRIPTION_ID
           valueFrom:
             configMapKeyRef:
               name: hyperfleet-broker-config
-              key: SUBSCRIPTION_ID
+              key: BROKER_SUBSCRIPTION_ID
🧹 Nitpick comments (27)
OWNERS (1)

2-2: Inconsistent quoting style in approvers list.

The approver entry "86254860" is quoted while all other approvers in the list are unquoted. For consistency, either quote all numeric identifiers or match the unquoted style of the rest of the file.

If the quotes are necessary for a specific tooling reason (e.g., to ensure the entry is treated as a string rather than a number), please clarify; otherwise, consider removing them.

test/integration/testutil/container.go (1)

252-287: Consider cleaning up leaked containers from both runtimes.

The function returns early on line 285 after successfully processing the first runtime. If leaked containers exist in both docker and podman environments (e.g., when both are installed), only containers from the first runtime will be cleaned up.

Consider removing the early return to process both runtimes:

 		}
-		return // Success with this runtime
 	}
 }

This ensures all leaked containers are cleaned up regardless of which runtime(s) they're running under.

configs/templates/deployment.yaml (1)

24-24: Consider pinning the container image tag.

The template uses quay.io/hyperfleet/controller:latest, which can lead to inconsistent deployments and makes it difficult to track which version is running. Consider using a specific version tag or SHA digest.

-          image: "quay.io/hyperfleet/controller:latest"
+          image: "quay.io/hyperfleet/controller:v1.0.0"  # Or use SHA: @sha256:...
configs/templates/job.yaml (1)

18-18: Consider pinning the validator image tag.

Similar to the deployment template, using :latest for the validator image can lead to inconsistent validation behavior across different job runs.

-          image: "quay.io/hyperfleet/validator:latest"
+          image: "quay.io/hyperfleet/validator:v1.0.0"  # Or use SHA: @sha256:...
internal/hyperfleet_api/README.md (2)

67-72: Misleading example: body passed twice.

The example passes body both as the second argument to Post and via WithJSONBody(body). This could confuse users about the intended usage. If Post already accepts a body argument, WithJSONBody may be redundant or override it.

Consider clarifying whether WithJSONBody sets the Content-Type header only, or if one of the body parameters should be removed:

 // POST with JSON body
 body, _ := json.Marshal(payload)
-resp, err := client.Post(ctx, url, body,
-    hyperfleet_api.WithJSONBody(body),
-)
+resp, err := client.Post(ctx, url, body)

78-96: Example silently ignores parse error.

Line 82 discards the error from time.ParseDuration. While this is example code, it could mislead users into adopting bad practices. Consider showing proper error handling:

 if apiConfig.Timeout != "" {
-    timeout, _ := time.ParseDuration(apiConfig.Timeout)
-    opts = append(opts, hyperfleet_api.WithTimeout(timeout))
+    timeout, err := time.ParseDuration(apiConfig.Timeout)
+    if err != nil {
+        return nil, fmt.Errorf("invalid timeout: %w", err)
+    }
+    opts = append(opts, hyperfleet_api.WithTimeout(timeout))
 }
internal/config_loader/loader_e2e_test.go (1)

10-15: Relative paths may be fragile for test execution.

The test uses relative paths like "../../configs/adapter-config-template.yaml". While this works when running from the package directory, it may fail if tests are executed from the repository root or other locations.

Consider using os.Getwd() with path resolution or a test helper that locates the repository root reliably.

cmd/adapter/main.go (2)

73-91: Consider validating base URL before creating client.

The API base URL is validated after the client is created (lines 83-88). Since BaseURLFromEnv() is a simple env lookup, validating it before creating the client would provide faster feedback and avoid creating a client that cannot be used.

+	// Verify API base URL is configured first
+	apiBaseURL := hyperfleet_api.BaseURLFromEnv()
+	if apiBaseURL == "" {
+		log.Error(fmt.Sprintf("HyperFleet API base URL not configured. Set %s environment variable", hyperfleet_api.EnvBaseURL))
+		return fmt.Errorf("HyperFleet API base URL not configured")
+	}
+
 	// Create HyperFleet API client from config
 	// The client is stateless and safe to reuse across messages.
 	// Each API call receives the message-specific context for proper isolation.
 	log.Info("Creating HyperFleet API client...")
 	apiClient, err := createAPIClient(adapterConfig.Spec.HyperfleetAPI)
 	if err != nil {
 		log.Error(fmt.Sprintf("Failed to create HyperFleet API client: %v", err))
 		return fmt.Errorf("failed to create HyperFleet API client: %w", err)
 	}
-
-	// Verify API base URL is configured
-	apiBaseURL := hyperfleet_api.BaseURLFromEnv()
-	if apiBaseURL == "" {
-		log.Error(fmt.Sprintf("HyperFleet API base URL not configured. Set %s environment variable", hyperfleet_api.EnvBaseURL))
-		return fmt.Errorf("HyperFleet API base URL not configured")
-	}

141-162: Event handler scaffold is well-documented.

The handler includes clear TODO comments outlining the intended processing steps. The blank assignments (_ = adapterConfig, _ = apiClient) are an acceptable pattern for scaffolding code.

Would you like me to help generate a skeleton implementation for the event processing steps outlined in the TODO comments?

internal/hyperfleet_api/client_test.go (2)

646-659: Prefer strings.Contains over custom implementation.

The custom contains and containsHelper functions replicate strings.Contains from the standard library. Using the standard library is simpler and more readable:

+import "strings"
+
 // In test assertions:
-if !contains(errStr, "POST") {
+if !strings.Contains(errStr, "POST") {

This would eliminate lines 646-659 entirely.


661-677: Prefer io.ReadAll over custom implementation.

The custom readAll function can be replaced with io.ReadAll from the standard library:

+import "io"
+
 // In TestClientPost:
-receivedBody, err = readAll(r.Body)
+receivedBody, err = io.ReadAll(r.Body)

This eliminates the custom implementation and handles EOF correctly.

pkg/errors/api_error.go (1)

120-153: Consider nil receiver safety for status code helpers.

The status code helper methods (IsTimeout, IsServerError, etc.) are called on pointer receivers but don't check for nil. If called on a nil *APIError, these would panic.

While unlikely in practice since constructors return non-nil pointers, adding nil checks would make the API more robust:

 func (e *APIError) IsTimeout() bool {
+	if e == nil {
+		return false
+	}
 	return e.StatusCode == 408 || errors.Is(e.Err, context.DeadlineExceeded)
 }

Alternatively, document that methods expect non-nil receivers.

internal/config_loader/validator_schema.go (3)

303-325: Consider path traversal protection for file references.

The validateFileExists function resolves paths but doesn't validate that the resolved path stays within the expected directory. A malicious config could reference ../../etc/passwd or similar paths.

Consider adding path containment validation:

 func (v *SchemaValidator) validateFileExists(refPath, configPath string) error {
 	if refPath == "" {
 		return fmt.Errorf("%s: file reference is empty", configPath)
 	}

 	fullPath := v.resolvePath(refPath)

+	// Ensure resolved path is within baseDir (prevent path traversal)
+	if v.baseDir != "" {
+		absBaseDir, err := filepath.Abs(v.baseDir)
+		if err != nil {
+			return fmt.Errorf("%s: failed to resolve base directory: %w", configPath, err)
+		}
+		if !strings.HasPrefix(fullPath, absBaseDir+string(filepath.Separator)) && fullPath != absBaseDir {
+			return fmt.Errorf("%s: file reference %q escapes base directory", configPath, refPath)
+		}
+	}
+
 	// Check if file exists
 	info, err := os.Stat(fullPath)

408-414: resolvePath should use filepath.Clean to normalize paths.

While filepath.Join handles some normalization, explicitly cleaning the result helps prevent path traversal via sequences like foo/../../../etc.

 func (v *SchemaValidator) resolvePath(refPath string) string {
 	if filepath.IsAbs(refPath) {
-		return refPath
+		return filepath.Clean(refPath)
 	}
-	return filepath.Join(v.baseDir, refPath)
+	return filepath.Clean(filepath.Join(v.baseDir, refPath))
 }

466-500: Legacy wrapper functions create unnecessary allocations.

Each legacy wrapper creates a new SchemaValidator instance with an empty baseDir. While this is acceptable for backward compatibility, consider documenting that these are deprecated or internal-only.

internal/criteria/cel_evaluator_test.go (1)

332-355: Consider adding error case tests for formatCELValue.

The tests cover successful formatting but don't test unsupported types that might cause errors.

Consider adding a test case for an unsupported type:

{
    name:    "unsupported type",
    value:   struct{}{},
    wantErr: true,
},
internal/config_loader/loader.go (1)

148-195: Consider wrapping adapter version validation error for consistency.

All other validation errors are wrapped with context (e.g., "validation failed:", "file reference validation failed:"), but adapter version validation error is returned unwrapped.

 	// Adapter version validation (optional)
 	if cfg.adapterVersion != "" {
 		if err := ValidateAdapterVersion(config, cfg.adapterVersion); err != nil {
-			return err
+			return fmt.Errorf("adapter version validation failed: %w", err)
 		}
 	}
internal/config_loader/validator.go (1)

385-388: CEL type-check result is discarded silently.

The comment explains this is acceptable for dynamic variables, but consider logging a warning at a verbose level when type-checking fails, as it could indicate real issues.

 	// Try to check the expression (type checking)
 	// Note: This may fail for dynamic variables, which is acceptable
-	_, _ = v.celEnv.Check(ast)
+	_, iss := v.celEnv.Check(ast)
+	if iss != nil && iss.Err() != nil {
+		// Log at debug level - type check failures are expected for dynamic types
+		// but could indicate real issues in some cases
+	}
internal/hyperfleet_api/client.go (3)

54-61: Potential nil pointer dereference in option functions.

Options like WithDefaultHeader, WithTimeout, etc., access c.config without nil checks. If NewClient is modified or options are applied in unexpected ways, this could panic.

Consider initializing config before applying options, which is already done in NewClient at line 123. Document that options should only be used with NewClient.


108-118: Silent no-op when HYPERFLEET_API_TOKEN is empty.

WithBearerTokenFromEnv silently does nothing if the env var is unset. This could lead to confusing auth failures. Consider logging or providing a variant that returns an error.

// WithBearerTokenFromEnvRequired returns an error during client use if token is missing
func WithBearerTokenFromEnvRequired() (ClientOption, error) {
    token := os.Getenv(EnvAPIToken)
    if token == "" {
        return nil, fmt.Errorf("%s environment variable is required", EnvAPIToken)
    }
    return WithBearerToken(token), nil
}

302-333: Backoff calculation has potential for negative jitter values.

The jitter calculation rand.Float64()*0.2*float64(delay) - 0.1*float64(delay) produces values in range [-0.1*delay, +0.1*delay], which could theoretically result in a negative delay if the base delay is very small.

 	// Add jitter (±10%) to prevent thundering herd
 	jitter := time.Duration(rand.Float64()*0.2*float64(delay) - 0.1*float64(delay))
 	delay += jitter

+	// Ensure delay is positive
+	if delay < 0 {
+		delay = 0
+	}
+
 	// Cap at max delay
internal/criteria/cel_evaluator.go (2)

108-113: Type-check failure is silently ignored.

When type-checking fails (line 111-112), the code falls back to using the parsed AST without propagating any warning. This could hide expression issues that would only surface at runtime evaluation.

Consider logging a warning when type checking fails but evaluation proceeds:

 	checked, issues := e.env.Check(ast)
 	if issues != nil && issues.Err() != nil {
 		// Use parsed AST if type checking fails (common with dynamic types)
+		// Note: This is expected for dynamic types but could indicate issues
 		checked = ast
 	}

272-277: String slice escaping duplicates logic from string case.

The []string case duplicates the string escaping logic from the string case above. Consider using formatCELValue recursively for consistency.

 	case []string:
 		items := make([]string, len(v))
 		for i, item := range v {
-			items[i] = fmt.Sprintf(`"%s"`, strings.ReplaceAll(item, `"`, `\"`))
+			formatted, err := formatCELValue(item)
+			if err != nil {
+				return "", err
+			}
+			items[i] = formatted
 		}
 		return fmt.Sprintf("[%s]", strings.Join(items, ", ")), nil
internal/config_loader/types.go (1)

63-69: Inconsistent field naming: observed_generation uses snake_case.

The field ObservedGeneration has a YAML tag observed_generation (snake_case) while other fields like lastUpdated use camelCase. Consider standardizing on one convention.

-	ObservedGeneration *ValueField               `yaml:"observed_generation,omitempty"`
+	ObservedGeneration *ValueField               `yaml:"observedGeneration,omitempty"`

Note: This would require updating the test YAML files accordingly.

test/testdata/adapter_config_valid.yaml (1)

81-83: exists operator with value: true may be redundant.

The exists operator typically doesn't need a value parameter - its purpose is to check field existence. The value: true may be ignored or could cause confusion.

Consider simplifying:

         - field: "vpcId"
           operator: "exists"
-          value: true
internal/criteria/evaluator.go (2)

215-240: CEL evaluator instantiated on every call - consider caching.

Each EvaluateCEL* method creates a new CELEvaluator instance. If these methods are called frequently, this could impact performance due to repeated environment setup.

Consider caching the CEL evaluator instance:

 type Evaluator struct {
 	context  *EvaluationContext
+	celEval  *CELEvaluator
+	celErr   error
 }

+func (e *Evaluator) getCELEvaluator() (*CELEvaluator, error) {
+	if e.celEval == nil && e.celErr == nil {
+		e.celEval, e.celErr = NewCELEvaluator(e.context)
+	}
+	return e.celEval, e.celErr
+}

 func (e *Evaluator) EvaluateCEL(expression string) (*CELResult, error) {
-	celEval, err := NewCELEvaluator(e.context)
+	celEval, err := e.getCELEvaluator()
 	if err != nil {
 		return nil, err
 	}
 	return celEval.Evaluate(expression)
 }

Note: If the context data changes between calls, caching may not be appropriate without invalidation logic.


85-100: Redundant return in EvaluateConditionSafe.

Lines 94-97 return false for field not found, then line 97 also returns false. The second return is unreachable from the IsFieldNotFound branch but the structure is slightly confusing.

 func (e *Evaluator) EvaluateConditionSafe(field string, operator Operator, value interface{}) bool {
 	result, err := e.EvaluateConditionWithResult(field, operator, value)
 	if err != nil {
 		// For "exists" operator, field not found means not exists
 		if operator == OperatorExists {
 			return false
 		}
-		// For other operators, check if it's a field not found error
-		if IsFieldNotFound(err) {
-			return false
-		}
+		// Any error (including field not found) returns false
 		return false
 	}
 	return result.Matched
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c939049 and 987308f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (33)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/adapter-config-template.yaml (0 hunks)
  • configs/broker-configmap-pubsub-template.yaml (1 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_e2e_test.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • configs/adapter-config-template.yaml
🧰 Additional context used
🧬 Code graph analysis (18)
internal/hyperfleet_api/client_test.go (3)
internal/hyperfleet_api/client.go (2)
  • NewClient (121-139)
  • ClientOption (37-37)
internal/hyperfleet_api/types.go (3)
  • ClientConfig (38-51)
  • DefaultClientConfig (54-63)
  • Response (156-169)
pkg/errors/api_error.go (2)
  • NewAPIError (121-132)
  • IsAPIError (142-148)
internal/config_loader/loader_e2e_test.go (3)
internal/config_loader/loader.go (1)
  • Load (85-108)
internal/config_loader/types.go (3)
  • Metadata (12-16)
  • APICall (114-122)
  • ExtractField (131-134)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/config_loader/loader_test.go (3)
internal/config_loader/loader.go (7)
  • Load (85-108)
  • Parse (111-127)
  • LoadWithVersion (131-133)
  • ParseWithVersion (137-139)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (59-63)
internal/config_loader/types.go (9)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
  • AdapterConfig (4-9)
  • AdapterConfigSpec (19-27)
  • Parameter (47-61)
  • Resource (144-151)
  • PostConfig (167-170)
  • DiscoveryConfig (154-158)
  • SelectorConfig (161-164)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (448-460)
  • IsSupportedAPIVersion (438-445)
test/integration/testutil/container.go (1)
test/integration/broker_consumer/testutil_container.go (1)
  • setupPubSubEmulatorContainer (30-73)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (41-48)
  • ConditionDef (271-275)
internal/config_loader/validator_test.go (3)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/config_loader/validator.go (1)
  • ValidationErrors (28-30)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-61)
  • Resource (144-151)
  • Precondition (104-111)
  • PostAction (173-177)
  • HyperfleetAPIConfig (35-39)
cmd/adapter/main.go (3)
internal/config_loader/loader.go (3)
  • EnvConfigPath (22-22)
  • Load (85-108)
  • WithAdapterVersion (52-56)
internal/config_loader/types.go (2)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
internal/hyperfleet_api/client.go (8)
  • BaseURLFromEnv (142-144)
  • EnvBaseURL (21-21)
  • ClientOption (37-37)
  • WithTimeout (64-68)
  • WithRetryAttempts (71-75)
  • WithRetryBackoff (78-82)
  • WithBearerTokenFromEnv (109-118)
  • NewClient (121-139)
internal/criteria/evaluator_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
  • EvaluationError (97-101)
internal/criteria/evaluator.go (3)
  • NewEvaluator (41-48)
  • IsFieldNotFound (527-530)
  • FieldNotFoundError (516-520)
internal/hyperfleet_api/types.go (1)
pkg/errors/error.go (1)
  • HyperFleetAPIError (207-209)
internal/config_loader/validator.go (3)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (114-122)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/hyperfleet_api/client.go (2)
internal/hyperfleet_api/types.go (4)
  • Client (214-232)
  • ClientConfig (38-51)
  • Request (70-85)
  • Response (156-169)
pkg/errors/api_error.go (1)
  • NewAPIError (121-132)
internal/criteria/cel_evaluator.go (2)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
test/integration/config-loader/config_criteria_integration_test.go (4)
internal/config_loader/loader.go (1)
  • Load (85-108)
internal/criteria/types.go (4)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorExists (26-26)
  • OperatorGreaterThan (22-22)
internal/criteria/evaluator.go (2)
  • NewEvaluator (41-48)
  • ConditionDef (271-275)
internal/criteria/cel_evaluator.go (2)
  • ConditionsToCEL (297-312)
  • ConditionToCEL (199-241)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (448-460)
internal/config_loader/validator.go (1)
  • Validate (462-464)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (32-49)
  • ConditionToCEL (199-241)
  • ConditionsToCEL (297-312)
internal/criteria/evaluator.go (2)
  • ConditionDef (271-275)
  • NewEvaluator (41-48)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (6)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • OperatorExists (26-26)
  • EvaluationError (97-101)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (4)
  • CELResult (20-29)
  • NewCELEvaluator (32-49)
  • ConditionToCEL (199-241)
  • ConditionsToCEL (297-312)
🔇 Additional comments (102)
test/integration/testutil/container.go (3)

9-10: LGTM!

The new imports are appropriately used for CLI-based container cleanup operations.


170-174: Verify the 60-second minimum cleanup timeout is appropriate.

The cleanup timeout is clamped to a minimum of 60 seconds. While this helps prevent stuck containers, it may be excessive for fast-running tests in CI environments. Consider whether this minimum should be:

  • Configurable via ContainerConfig
  • Environment-variable driven (e.g., CI vs local)
  • Or confirmed as intentional to handle worst-case scenarios

221-250: LGTM!

The force cleanup function provides a robust fallback mechanism with proper error handling and helpful logging. The function correctly tries both docker and podman runtimes and filters out noise from already-removed containers.

test/testdata/templates/deployment.yaml (1)

1-27: LGTM! Test template structure is valid.

The test deployment template is well-structured with proper label selectors and templating syntax. The use of nginx:latest is acceptable for test data.

configs/templates/deployment.yaml (1)

30-36: Resource configuration looks reasonable.

The resource requests and limits provide appropriate burst capacity for a controller workload.

configs/templates/job.yaml (1)

11-15: Job configuration is correct.

The backoffLimit: 3 and restartPolicy: Never settings are appropriate for a validation Job.

configs/templates/cluster-status-payload.yaml (2)

7-15: Good use of default values for resilience.

The template appropriately uses default fallbacks for condition fields, ensuring the payload is valid even when some values are missing.


6-6: Template functions are available — original concern appears unfounded.

The web search confirms that hyperfleet-adapter uses Go templates with Sprig template helpers, which include both now and date functions. These are standard, documented functions in the project. The format string "2006-01-02T15:04:05Z07:00" is also correct for Go's reference time layout. The template syntax in the code is valid and matches documented usage patterns.

go.mod (1)

7-7: Docker dependency reorganization looks appropriate.

The change from github.com/docker/docker to github.com/docker/go-connections as a direct dependency, with docker/docker moved to indirect, likely resolves version conflicts or clarifies actual usage.

internal/criteria/types.go (5)

7-39: Well-defined operator set.

The operator enumeration is comprehensive and well-documented, covering comparison, membership, and existence checks.


41-58: Helper functions are correct.

IsValidOperator and OperatorStrings provide clean validation and conversion utilities. The linear search in IsValidOperator is appropriate given the small number of operators.


89-94: Note: Merge performs shallow copy.

The Merge method performs a shallow copy of values from the other context. If the map values are pointers, slices, or maps, modifications to nested data will be shared between contexts. This may be intentional, but ensure callers are aware of this behavior.


96-112: Error type implementation follows best practices.

EvaluationError correctly implements the error interface with Unwrap() support for error chain compatibility.


85-87: Manual verification needed: Confirm getNestedField helper is defined.

GetNestedField calls getNestedField(c.Data, path), but this helper function could not be located through automated search. Please confirm this function is properly defined elsewhere in the internal/criteria package (likely in a sibling file such as evaluator.go or helpers.go) and is accessible to types.go.

internal/hyperfleet_api/types.go (5)

12-31: Backoff configuration is well-designed.

The three backoff strategies (exponential, linear, constant) and default values (10s timeout, 3 retries, exponential backoff) provide sensible retry behavior for API clients.


37-63: ClientConfig structure is comprehensive.

The configuration covers all necessary HTTP client parameters with sensible defaults and proper map initialization.


69-149: Request options follow Go best practices.

The functional options pattern and use of pointer types for optional overrides enable flexible per-request configuration without ambiguity.


187-199: IsRetryable logic is well-considered.

The retry logic appropriately includes transient failures (408, 429, 5xx) and gateway errors (502, 503, 504), while avoiding retrying client errors that won't succeed.


213-232: Client interface design is clean and idiomatic.

The interface provides a core Do method with convenience wrappers for standard HTTP verbs, all properly context-aware.

configs/broker-configmap-pubsub-template.yaml (1)

24-28: Key renaming improves clarity.

Adding the BROKER_ prefix to SUBSCRIPTION_ID and TOPIC clearly distinguishes adapter-specific configuration from broker library configuration.

internal/config_loader/loader_e2e_test.go (1)

103-111: LGTM!

The helper function correctly iterates by index and returns a pointer to the slice element, avoiding the common pitfall of returning a pointer to the loop variable.

internal/config_loader/README.md (1)

1-295: Comprehensive config_loader documentation.

The README effectively documents the package's capabilities, including loading, parsing, validation, and accessing configuration data. The YAML structure examples align with the type definitions in types.go.

cmd/adapter/main.go (1)

187-219: LGTM!

The createAPIClient function properly validates configuration values, includes error handling for invalid timeout formats and unknown backoff strategies, and uses the environment-based token configuration.

internal/criteria/evaluator_scenarios_test.go (2)

10-89: Comprehensive real-world scenario tests.

The TestRealWorldScenario test provides excellent coverage of precondition evaluation patterns that match the adapter config template. The tests properly validate individual conditions and combined evaluation using EvaluateConditions.


220-299: Well-structured table-driven test for node count validation.

The test correctly uses OperatorGreaterThan(minNodes-1) to simulate >= and OperatorLessThan(maxNodes+1) to simulate <=, with boundary cases properly covered.

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

339-394: Excellent null safety coverage.

The tests thoroughly validate the evaluator's safe access methods (GetFieldSafe, GetFieldOrDefault, HasField, EvaluateConditionSafe) with various null scenarios including nil resources and deeply nested nulls.

internal/hyperfleet_api/client_test.go (2)

198-271: Thorough retry logic testing.

The retry tests properly validate:

  • Successful retry after transient failures (TestClientRetry)
  • Retry exhaustion behavior (TestClientRetryExhausted)
  • No retry on 4xx client errors (TestClientNoRetryOn4xx)

Good use of atomic.AddInt32 for thread-safe attempt counting.


382-422: Comprehensive backoff calculation tests.

The tests verify all three backoff strategies (exponential, linear, constant) with appropriate tolerance for jitter (±20%). This ensures the backoff implementation behaves correctly across different retry scenarios.

internal/criteria/README.md (1)

1-381: Verification inconclusive due to repository access issue.

The verification request cannot be completed because the repository failed to clone. The review comment requests confirmation that internal/k8s-client exists as a related package (referenced on line 362 of the README). Without access to the codebase, I cannot definitively determine whether:

  1. The internal/k8s-client package exists
  2. The reference is accurate or planned for future implementation
  3. The documentation should be updated

This requires manual verification of the codebase structure.

internal/config_loader/validator_test.go (8)

1-10: LGTM!

Clean imports and package declaration. Good use of testify for assertions with both assert and require packages for different assertion needs.


12-120: Well-structured operator validation tests.

Good coverage of valid operators, invalid operators, and missing operator scenarios. The table-driven test pattern is idiomatic Go.


122-268: Comprehensive template variable validation tests.

Tests cover defined variables, undefined variables in URLs, undefined variables in manifests, and nested variable access from stored responses. The test for storeResponseAs with extract fields properly validates the variable resolution chain.


270-366: CEL expression validation tests look good.

Covers valid expressions, syntax errors, and valid usage of CEL functions like has(). The syntax error test with ==== effectively validates parse error detection.


368-578: Thorough K8s manifest validation tests.

Good coverage of all required fields: apiVersion, kind, metadata, and metadata.name. Also tests manifest refs (both valid and empty). The tests properly validate the hierarchical K8s manifest structure requirements.


580-590: Operator validation test verifies criteria package integration.

This test ensures the criteria package's operator registry contains all expected operators, validating the integration between config_loader and criteria packages.


592-646: ValidationErrors and Validate tests are well-designed.

The ValidationErrors test validates error aggregation and formatting. The Validate test intentionally triggers multiple validation errors to ensure they are collected and reported together.


648-679: Builtin variables test validates metadata references.

Tests that .metadata.name is recognized as a builtin variable, ensuring template validation doesn't flag these as undefined.

internal/config_loader/loader_test.go (13)

1-10: LGTM!

Clean imports including os and filepath for file operations needed in the tests.


12-78: Load and LoadInvalidPath tests are well-implemented.

Good use of t.TempDir() for automatic cleanup. The test covers basic loading functionality and validates key fields after parsing.


80-174: Parse validation tests cover essential schema requirements.

Tests for missing apiVersion, kind, metadata.name, and adapter.version provide good baseline validation coverage.


176-261: Parameter validation tests are comprehensive.

Covers valid parameters with source, parameters missing name, and parameters missing source/build/buildRef/fetchExternalResource. The error messages match the expected validation logic.


263-395: Precondition validation tests cover all key scenarios.

Tests valid preconditions, missing name, missing apiCall/expression/conditions, missing method, and invalid HTTP method. Good coverage of the precondition validation logic.


397-489: Resource validation tests are well-structured.

Covers valid resources with manifest, resources missing name, and resources missing manifest.


491-603: Accessor method tests verify helper functionality.

Tests for GetRequiredParams, GetResourceByName, and GetPreconditionByName verify the accessor methods return correct data and handle missing entries gracefully.


605-759: Version and API version tests are thorough.

Tests ParseTimeout, unsupported API version detection, invalid kind detection, LoadWithVersion, ParseWithVersion, ValidateAdapterVersion, and IsSupportedAPIVersion. Good coverage of version-related functionality.


761-890: File reference validation tests are comprehensive.

Tests valid and invalid buildRef paths, manifest.ref paths, directory references, and post.params buildRef. Proper use of temporary directories and file creation for realistic testing.


892-983: File reference content loading tests validate end-to-end behavior.

Tests that buildRef and manifest.ref files are properly loaded and their content is embedded into the config structure.


985-1066: LoadFileReferencesContent test validates content replacement.

Verifies that manifest.ref content replaces the ref field and buildRef content is loaded into BuildRefContent while preserving the original path.


1068-1263: Resource discovery validation tests are thorough.

Covers all discovery validation scenarios: bySelectors, byName, missing discovery, missing namespace, missing byName/bySelectors, empty selectors, and manifest.refs array.


1265-1335: Manifest refs array loading test validates multi-file loading.

Tests that manifest.refs array loads multiple template files and populates ManifestItems correctly.

internal/config_loader/accessors.go (8)

1-18: LGTM!

Clean package declaration and BuiltinVariables function. The list of builtin variables is well-documented for template/CEL usage.


24-71: GetDefinedVariables implementation is comprehensive.

Properly collects variables from all sources: builtins, params, precondition extracts/storeResponseAs, post params, and resource aliases. The nil checks on c.Spec.Post prevent panics.


73-98: GetParamByName and GetRequiredParams are correctly implemented.

Uses range over indices to return pointers, avoiding copying issues. Good nil safety.


100-137: Resource and Precondition accessors are well-implemented.

GetResourceByName, GetPreconditionByName, and GetPostActionByName all follow the same safe pattern with nil checks and pointer returns.


139-161: ParamNames and ResourceNames return ordered slices.

Pre-allocates slices with known capacity for efficiency.


167-174: ParseTimeout handles empty and nil cases gracefully.

Returns 0, nil when timeout is empty, allowing callers to apply defaults. Uses standard time.ParseDuration for parsing.


180-221: Manifest ref helpers handle both single and array refs.

HasManifestRef checks for either "ref" or "refs" keys. GetManifestRefs correctly handles both cases and filters empty strings.


223-239: UnmarshalManifest provides clear error messaging.

Returns nil, nil for nil resource/manifest, and includes type information in error message when manifest is not a map.

pkg/errors/api_error.go (4)

1-33: Well-designed APIError struct with comprehensive fields.

Captures all relevant context: HTTP method, URL, status code, response body, retry attempts, total duration, and underlying error. Good documentation on each field.


35-48: Error() and Unwrap() implementations are correct.

Error message format differs based on whether a status code was received. Unwrap() enables proper error chain handling with errors.Is and errors.As.


54-97: Status code helpers provide convenient error classification.

All HTTP status code checks are correct. IsTimeout properly checks both 408 status and context deadline exceeded.


103-114: Response body helpers are nil-safe.

ResponseBodyString returns empty string for nil body, HasResponseBody checks length.

internal/criteria/evaluator_test.go (9)

1-10: LGTM!

Clean package declaration and imports for test file.


11-111: EvaluationContext tests are comprehensive.

Covers creation, basic Set/Get, nested field access with various depths, error cases for nonexistent fields, and context merging with key overwriting behavior.


113-270: Evaluation function tests cover key operators.

Tests for equals, in, and contains operators cover string/number comparisons, nil handling, array membership, and error cases. Good edge case coverage including "both nil" for equals.


272-426: Numeric comparison and exists operator tests are thorough.

Tests greaterThan, lessThan with int/float values, equal values (boundary case), and non-numeric error handling. Exists operator tests cover all value types: strings, nil, slices, maps, numbers.


428-567: Evaluator condition tests exercise the full API.

Tests all operators through the Evaluator interface, including nested field access and nonexistent field error handling. EvaluateConditions test verifies AND semantics across multiple conditions.


569-665: getNestedField and toFloat64 tests validate internal helpers.

Comprehensive path navigation testing and numeric type conversion for all int/uint/float variants.


667-740: Error type and accessor method tests are well-designed.

EvaluationError formatting, nil context handling, GetField, and GetFieldOrDefault tests validate error messages and default value fallback behavior.


742-873: Result-based evaluation tests verify rich return data.

EvaluateConditionWithResult and EvaluateConditionsWithResult tests validate that results contain matched status, field values, operator info, and extracted fields. Good verification that extracted fields are populated even when conditions fail.


875-992: Null handling tests are thorough and important.

Tests cover accessing fields on null parents, safe get returning nil, defaults on null paths, HasField semantics with nulls, and deep null path navigation. This is critical for robust JSON/YAML data handling.

internal/config_loader/validator_schema.go (3)

16-29: LGTM: Clean SchemaValidator design with functional approach.

The struct encapsulates validation state cleanly, and the constructor follows Go conventions.


31-51: LGTM: Well-structured validation pipeline with fail-fast semantics.

The validator chain pattern is clean and extensible. Fail-fast behavior is appropriate for structural validation.


392-406: Based on the web search results, I can now provide a verified assessment of this security concern.

The web search confirms that gopkg.in/yaml.v3 does NOT have known arbitrary code execution (RCE) vulnerabilities like some YAML libraries in other languages (PyYAML, js-yaml). The original review comment's concern about "YAML deserialization attacks" leading to code execution is not valid for this library.

However, the search did reveal:

  1. CVE-2022-28948 / GO-2022-0603 — older versions (< v3.0.1) can panic/crash on malformed input (DoS risk)
  2. The actual risks with untrusted YAML are DoS and resource exhaustion, not RCE
  3. The recommendation is to upgrade to v3.0.1+ and validate/sandbox untrusted YAML input

Unmarshaling into map[string]interface{} is safe from code execution with gopkg.in/yaml.v3, but verify the library version is ≥ v3.0.1 to mitigate DoS risks from malformed YAML.

The gopkg.in/yaml.v3 library does not enable arbitrary code execution like some YAML deserializers in other languages (PyYAML, js-yaml). However, DoS/crash vulnerabilities exist in versions before v3.0.1 (CVE-2022-28948). Ensure the dependency is updated to v3.0.1 or later. Additionally, if this function processes untrusted YAML, add input validation and size limits to prevent resource exhaustion attacks.

internal/criteria/cel_evaluator_test.go (4)

10-18: LGTM: Clean test setup for CELEvaluator initialization.

Good verification that the evaluator can be created with a populated context.


30-111: Comprehensive table-driven tests for CEL evaluation.

Good coverage of string equality, numeric comparisons, boolean variables, compound expressions, list membership, empty expressions, and syntax errors.


113-136: Good test coverage for nested data structures.

Tests verify that CEL can access deeply nested map structures, which is essential for evaluating Kubernetes resource fields.


175-260: LGTM: Thorough ConditionToCEL conversion tests.

Coverage includes all operators: equals, notEquals, greaterThan, lessThan, in, notIn, contains, exists, and invalid operator error handling.

internal/config_loader/loader.go (2)

42-70: LGTM: Clean functional options pattern implementation.

The options provide good flexibility for configuring loader behavior. Default values are handled well.


81-108: LGTM: Well-designed Load function with sensible defaults.

The automatic baseDir inference from config file path is a good UX improvement. Prepending the option allows user overrides.

internal/config_loader/validator.go (5)

17-49: LGTM: Well-designed validation error types.

The ValidationError and ValidationErrors types provide good structured error reporting with path context. The Add helper simplifies error collection.


72-95: LGTM: Clean validation orchestration.

The Validate method properly initializes context, runs all validators, and aggregates errors. Continuing after CEL env init failure allows other validations to proceed.


149-150: Template regex may miss some valid Go template patterns.

The regex \{\{\s*\.([a-zA-Z_][a-zA-Z0-9_\.]*)\s*(?:\|[^}]*)?\}\} handles basic cases but may not capture:

  • Template functions like {{ printf "%s" .var }}
  • Nested template actions like {{ range .items }}{{ .name }}{{ end }}
  • Variables without leading dot (e.g., {{ $var }})

Is this intentional to only validate .varName style references, or should broader template syntax be supported?


280-316: CEL environment initialization uses DynType appropriately.

Using cel.DynType for variables is correct when types aren't known at validation time. The deduplication of root variables prevents conflicts.


394-453: LGTM: Comprehensive K8s manifest validation.

Good checks for required fields (apiVersion, kind, metadata) and metadata.name. The handling of external refs (ref/refs) to skip inline validation is correct.

internal/hyperfleet_api/client.go (4)

30-34: LGTM: Clean client struct design.

The separation of http.Client and ClientConfig allows for flexible configuration and testing.


159-236: Well-implemented retry logic with proper context handling.

Good implementation:

  • Context cancellation checked before each attempt and during sleep
  • Proper distinction between success, non-retryable errors, and retryable errors
  • APIError returned with full context after exhausting retries

238-300: LGTM: Solid single-request implementation.

Good handling of:

  • Per-request timeout override
  • Default and request-specific headers
  • Automatic Content-Type for bodies
  • Response body reading with proper defer close

9-10: I encountered a repository access issue, but I was able to verify the key technical claim through web search. The Go 1.20+ auto-seeding of math/rand has been confirmed.

However, I cannot fully verify the review comment because:

  1. Repository access failed - I cannot examine the actual usage of math/rand in the codebase
  2. No context on Go version - Cannot determine the project's Go version requirements
  3. No usage verification - Cannot confirm if math/rand is actually being used for jitter/backoff, or if it's even used at all in this file

Given the constraints, here's the rewritten comment:


Verify if math/rand usage requires crypto/rand based on Go version and use case.

The comment assumes math/rand is used for jitter, but actual usage context is needed. Go 1.20+ auto-seeds the global math/rand generator at startup, making it acceptable for non-cryptographic use cases like backoff timing without explicit seeding. If the project targets Go <1.20 or uses math/rand for security-sensitive operations, consider crypto/rand instead. Confirm the project's Go version requirement and the specific purpose of these imports.

internal/criteria/cel_evaluator.go (3)

91-100: Empty expression returns true - verify this is intentional.

When an empty expression is passed to Evaluate, it returns Matched: true. While this may be a valid design decision for optional expressions, it could mask configuration errors where an expression was expected but not provided.

Consider whether this should log a warning or be configurable behavior.


31-49: LGTM - Defensive nil handling in constructor.

The NewCELEvaluator function properly handles a nil context by creating a new empty one, preventing nil pointer dereferences downstream.


224-241: Based on the authoritative CEL documentation from the web search, I have sufficient evidence to complete the verification. The original review comment contains a fundamental misunderstanding of how CEL's has() macro works.

has() macro correctly handles missing intermediate objects—no chaining needed.

The CEL has() macro performs a presence test without evaluating the member access chain. When intermediate objects are missing, has(a.b.c) returns false rather than raising an error. This is the explicit design of has(): to safely check field presence when fields or intermediates may be absent. The current code at lines 224-241 correctly generates has(parent.lastField) for nested fields, which is valid CEL syntax and handles the reviewer's concern automatically. The suggestion to chain multiple has() calls is unnecessary.

Likely an incorrect or invalid review comment.

internal/config_loader/types.go (2)

1-189: LGTM - Well-structured configuration model.

The type definitions are comprehensive and well-organized with clear YAML mappings. The use of yaml:"-" for loader-populated fields (BuildRefContent, ManifestItems) is appropriate.


34-39: I was unable to access the repository to verify the review comment about duration field parsing. The codebase clone operation failed, preventing me from searching for:

  • Whether ParseDuration is used to validate these string fields
  • How HyperfleetAPIConfig is actually instantiated and used
  • Whether validation happens at config load time
  • If there are existing guards against invalid duration strings like "2ss" or "abc"

Without access to the codebase, I cannot confirm whether the concern about parsing/validation is already addressed elsewhere or if it represents a genuine issue.

Duration fields are strings—ensure consistent parsing/validation.

Timeout and RetryBackoff are string types. Ensure there's consistent parsing/validation elsewhere (e.g., using time.ParseDuration) to catch invalid values like "2ss" or "abc" early.

test/testdata/adapter_config_valid.yaml (2)

1-202: LGTM - Comprehensive test configuration.

The test YAML file covers a wide range of configuration scenarios including parameters, preconditions with API calls, resources with inline manifests and external references, and post-processing with CEL expressions.


143-150: Thank you for that context. Let me search for more information about which CEL implementation this adapter configuration uses:
<function_calls>

#!/bin/bash

Search for documentation about the adapter format and CEL implementation

echo "=== Looking for README, docs, or specification files ==="
find . -type f ( -name ".md" -o -name ".txt" -o -name ".rst" -o -name ".adoc" ) -exec cat {} ; 2>/dev/null

echo -e "\n=== Checking for any comment in YAML files ==="
head -50 test/testdata/adapter_config_valid.yaml

echo -e "\n=== Searching for any reference to CEL or expression evaluation ==="
grep -r "CEL|cel|expression" . --include=".md" --include=".txt" --include="*.yaml" 2>/dev/null | head -30


</function_calls>

<function_calls>

#!/bin/bash

Check for any top-level documentation or setup files

echo "=== Listing all files in repository root ==="
ls -la

echo -e "\n=== Looking for any config or specification ==="
find . -maxdepth 2 -type f -name ".json" -o -name ".yaml" -o -name "*.yml" 2>/dev/null | head -20


</function_calls>

<function_calls>

#!/bin/bash

Check if there's a schema or validation file that explains what 'resources' should be

echo "=== Searching entire repository for 'schema', 'resources', or 'documentation' ==="
rg -i "schema|resources.*object|resources.*map|resources.*variable" -B2 -A2 | head -100

echo -e "\n=== Checking YAML for any comments about context or variables ==="
grep -n "#" test/testdata/adapter_config_valid.yaml | head -30


</function_calls>

<function_calls>

#!/bin/bash

Check for dependency/manifest files that might identify the project

echo "=== Looking for go.mod, package.json, or other manifest files ==="
find . -name "go.mod" -o -name "go.sum" -o -name "package.json" -o -name "pom.xml" -o -name "Makefile" -o -name ".gitignore" 2>/dev/null | xargs cat 2>/dev/null

echo -e "\n=== Checking git information ==="
git config --get remote.origin.url 2>/dev/null || echo "No git remote configured"

echo -e "\n=== Checking for any .github directory ==="
ls -la .github 2>/dev/null || echo "No .github directory"


</function_calls>

<function_calls>

#!/bin/bash

Let's look at the complete structure and content of the YAML more carefully

echo "=== Complete adapter_config_valid.yaml with context ==="
wc -l test/testdata/adapter_config_valid.yaml
echo "---"
cat test/testdata/adapter_config_valid.yaml | nl -ba

echo -e "\n=== Looking specifically at the structure around statusConditions ==="
grep -n "statusConditions|resources." test/testdata/adapter_config_valid.yaml | head -30


</function_calls>

Based on my comprehensive investigation, I must conclude that I cannot definitively verify this concern because:

  1. Repository contains only test data - No Go implementation code, evaluator logic, or CEL context setup code exists in this repository
  2. No documentation of CEL implementation - Cannot determine which CEL system is being used (Google IAM Conditions, Kubernetes, OPA, etc.) which is essential for understanding how variables are provided
  3. Filename suggests validity - The file is named adapter_config_valid.yaml, implying this configuration should be valid as-is
  4. No visible variable registration - The expressions assume resources exists in the evaluation context, but the code that provides this context is not in this repository

The review comment's concern is conceptually sound - if CEL expressions reference resources, that variable must be in the evaluation context. However, without seeing the actual Go code that loads this configuration and sets up the CEL environment, I cannot confirm whether:

  • The resources variable is properly registered (review comment invalid)
  • The resources variable is not registered (review comment valid)

CEL expressions reference the resources variable. This concern cannot be verified in isolation within this repository, as the actual implementation code that evaluates these expressions and provides the CEL context is not present. Verify against the actual evaluator/post-processor implementation code that uses this configuration.

internal/criteria/evaluator.go (6)

36-48: LGTM - Evaluator constructor with nil safety.

The NewEvaluator function properly initializes an empty context when nil is passed, consistent with the pattern used in NewCELEvaluator.


277-289: LGTM - Null-safe equality evaluation.

The evaluateEquals function correctly handles nil cases before using reflect.DeepEqual.


515-530: LGTM - Custom error type with helper function.

The FieldNotFoundError type and IsFieldNotFound helper provide a clean pattern for error handling in field traversal.


51-53: I'll help you verify whether the GetNestedField method exists on EvaluationContext. Let me search the codebase for this method and the type definition.
<function_calls>


#!/bin/bash

Search for GetNestedField method definition

ast-grep --pattern 'func ($_ *EvaluationContext) GetNestedField($) ($) {
$$$
}'


</function_calls>


#!/bin/bash

Also search with rg to see all references to GetNestedField

rg -n 'GetNestedField' --type go


</function_calls>


#!/bin/bash

Search for EvaluationContext type definition

rg -n 'type EvaluationContext' --type go -A 5


</function_calls>
<function_calls>


#!/bin/bash

Check the context field type in evaluator.go

rg -n 'context' internal/criteria/evaluator.go -B 2 -A 2 | head -50


</function_calls>


420-423: Unable to verify due to repository access issues.

The codebase cannot be accessed to verify whether getNestedField is truly unused. Multiple verification attempts failed during repository cloning. Manual inspection of the code would be required to:

  • Confirm whether getNestedField is called anywhere in the codebase
  • Verify the relationship between the standalone getNestedField function and EvaluationContext.GetNestedField()
  • Determine if this represents actual code duplication or if both functions serve distinct purposes

270-275: Manual verification of the ConditionDef duplicate definition claim is required.

Unable to access the repository to verify whether ConditionDef is defined in both internal/criteria/evaluator.go and referenced without proper import handling in cel_evaluator.go. The codebase could not be cloned for automated analysis.

To complete this verification:

  • Confirm ConditionDef is defined exactly once across the codebase
  • Verify cel_evaluator.go properly imports or references ConditionDef from the correct location
  • Check compilation succeeds without duplicate definition errors

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: 2

♻️ Duplicate comments (1)
internal/criteria/cel_evaluator.go (1)

243-294: String escaping in formatCELValue is still incomplete; use strconv.Quote for correctness

Only double quotes are escaped for string and []string values, so backslashes and control characters (\n, \r, \t, etc.) can produce invalid CEL string literals or change expression meaning. This was flagged in an earlier review and still applies.

Consider delegating to strconv.Quote, which handles all necessary escapes, and reusing formatCELValue for []string elements:

-import (
-	"fmt"
-	"reflect"
-	"strings"
-
-	"github.com/google/cel-go/cel"
-	"github.com/google/cel-go/common/types"
-	"github.com/google/cel-go/common/types/ref"
-)
+import (
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+
+	"github.com/google/cel-go/cel"
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+)
@@
 	switch v := value.(type) {
 	case string:
-		// Escape quotes in string
-		escaped := strings.ReplaceAll(v, `"`, `\"`)
-		return fmt.Sprintf(`"%s"`, escaped), nil
+		// Properly escape all special characters for CEL string literal
+		return strconv.Quote(v), nil
@@
 	case []string:
-		items := make([]string, len(v))
-		for i, item := range v {
-			items[i] = fmt.Sprintf(`"%s"`, strings.ReplaceAll(item, `"`, `\"`))
-		}
-		return fmt.Sprintf("[%s]", strings.Join(items, ", ")), nil
+		items := make([]string, len(v))
+		for i, item := range v {
+			formatted, err := formatCELValue(item)
+			if err != nil {
+				return "", err
+			}
+			items[i] = formatted
+		}
+		return fmt.Sprintf("[%s]", strings.Join(items, ", ")), nil

You can quickly validate CEL parsing of strings with special characters against the cel‑go docs or examples.

How does cel-go expect string literals to be escaped (backslashes, newlines, tabs), and is using Go's strconv.Quote compatible with CEL string syntax?
🧹 Nitpick comments (18)
test/integration/testutil/container.go (3)

135-170: Retry-time cleanup of partially started containers looks solid

The added logic to terminate a partially created container on a failed start attempt (and fall back to forceCleanupContainer when Terminate fails) is a good robustness improvement that should significantly reduce leaked containers between retries. One optional nicety would be to set container = nil after a successful terminate/force-cleanup so it’s more obvious that the previous handle is no longer expected to be usable, but that’s not functionally required here.


176-205: Improved cleanup timeout and ID-based fallback; consider aligning the CleanupTimeout docs

Capturing containerID and containerName for logging, plus enforcing a minimum 60s timeout for cleanup with an ID-based forceCleanupContainer fallback, makes teardown much more resilient. One thing to double‑check is the comment on ContainerConfig.CleanupTimeout (“default: 30s”): since StartContainer now enforces cleanupTimeout >= 60s, it may be worth updating that docstring to match the effective behavior and avoid confusion for callers tuning timeouts.


236-303: Wrap docker/podman CLI calls in contexts with timeouts to avoid hanging tests

Both forceCleanupContainer and CleanupLeakedContainers use bare exec.Command invocations. If the docker/podman CLI or its daemon hangs (e.g., misconfigured socket, stuck daemon), these calls can block indefinitely and stall the entire test run. Consider:

  • Using context.WithTimeout + exec.CommandContext for both the ps and rm -f commands (e.g., 30s per call).
  • Optionally logging a trimmed version of the CLI output alongside the error in the failure path to aid debugging.

Functionally this is already a best‑effort fallback, but adding timeouts would better match the goal of making test cleanup robust even when the container runtime is unhealthy.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

39-51: Cleanup implementation is reasonable; optionally reset state after terminate

Switching TestEnvPrebuilt.Cleanup to actually terminate the container with a bounded timeout is a good improvement and keeps the method semantics consistent with its name. As an optional refinement, after a successful terminate you could set e.Container = nil so any subsequent calls clearly see there is no active container and avoid attempting another terminate on a defunct handle.


136-147: Deferred setup-failure cleanup is helpful; add small safety/logging tweaks

The setupSuccess + defer pattern nicely ensures that if anything fails after the container starts, you still terminate it promptly instead of waiting for outer cleanup. A couple of minor improvements you might consider:

  • Guard the defer with a result != nil check in addition to result.Container != nil to be maximally defensive against future changes to StartContainer’s contract.
  • Log any error from result.Container.Terminate(ctx) so that repeated failures during teardown don’t go completely silent.

These are small polish items; the overall approach is sound and plays well with the existing t.Cleanup registered inside StartContainer (assuming Terminate remains idempotent).

Also applies to: 186-186

test/integration/k8s_client/helper_testcontainers_k3s.go (1)

95-106: Early t.Cleanup registration is a solid leak-prevention improvement

Registering t.Cleanup immediately after k3s.Run is a good move and ensures the container is torn down even if later require calls abort the test setup. Two minor, optional refinements you might consider:

  • The outer if k3sContainer != nil plus the inner check inside the closure are a bit redundant; keeping just the inner check in the closure is typically sufficient.
  • Right now _ = k3sContainer.Terminate(ctx) discards errors. For debugging leaks or teardown issues, it can be useful to log failures (e.g., via t.Logf or log.Infof) so you get a signal when cleanup is not fully successful.

Also, with this implicit t.Cleanup in place and the existing (*TestEnvTestcontainers).Cleanup method still available, ensure tests don’t routinely call both paths for the same instance (or make the explicit Cleanup idempotent) to avoid noisy double Terminate attempts.

internal/criteria/cel_evaluator.go (2)

64-88: Consider improving slice type inference beyond []interface{}

Right now only []interface{} is recognized as a list type; other common slices (e.g. []string, []int) fall back to cel.DynType. That’s fine functionally, but you could get better type‑checking and editor feedback by treating any slice/array (detected via reflection) as cel.ListType(cel.DynType) or a more specific element type.


198-241: exists operator behavior is reasonable but slightly opinionated

The exists operator maps to has(parent.field) for dotted paths and x != null && x != "" for top‑level fields. That’s a pragmatic choice, but note:

  • For non‑string scalars on the top level, comparing to "" relies on CEL’s cross‑type comparison rules and may not align with caller expectations.
  • For map‑backed data, has() semantics differ from proto/struct fields.

If you expect exists to be used on non‑string fields or map keys, it may be worth tightening the semantics (e.g., only != null for non‑strings, or using has(m[key]) for maps).

internal/criteria/types.go (1)

60-94: Consider making EvaluationContext more defensive around nil maps

NewEvaluationContext initializes Data, but if a caller constructs an EvaluationContext manually (or deserializes one) with Data == nil, Set/Merge will panic on assignment.

A small guard would make the type more robust:

func (c *EvaluationContext) Set(key string, value interface{}) {
-	c.Data[key] = value
+	if c.Data == nil {
+		c.Data = make(map[string]interface{})
+	}
+	c.Data[key] = value
}
 
 func (c *EvaluationContext) Merge(other *EvaluationContext) {
-	for k, v := range other.Data {
-		c.Data[k] = v
-	}
+	if other == nil {
+		return
+	}
+	if c.Data == nil {
+		c.Data = make(map[string]interface{})
+	}
+	for k, v := range other.Data {
+		c.Data[k] = v
+	}
}
configs/templates/deployment.yaml (1)

1-37: Deployment template wiring looks correct; consider parameterizing the image tag

The labels, selectors, namespace, and CLUSTER_ID/RESOURCE_ID env wiring are all consistent and should produce a valid controller Deployment.

For production use, you may want to make the image tag a template parameter (or at least pin it to a version) instead of :latest to improve reproducibility and rollback behavior.

configs/templates/job.yaml (1)

1-24: Validation Job template is structurally sound; you might also pin or template the image tag

The Job spec (restart policy, backoffLimit, labels, and CLUSTER_ID/GENERATION_ID envs) is coherent and matches the intended “per‑cluster validation” behavior.

As with the controller Deployment, consider parameterizing or pinning the quay.io/hyperfleet/validator:latest tag to avoid surprises from implicit image updates.

cmd/adapter/main.go (1)

83-91: Consider validating API base URL before creating the client.

The API base URL validation at lines 84-88 happens after createAPIClient is called at line 77. While the client is stateless and this may work, it would be cleaner to validate configuration prerequisites before creating the client to fail fast with a clear error message.

+	// Verify API base URL is configured before creating client
+	apiBaseURL := hyperfleet_api.BaseURLFromEnv()
+	if apiBaseURL == "" {
+		log.Error(fmt.Sprintf("HyperFleet API base URL not configured. Set %s environment variable", hyperfleet_api.EnvBaseURL))
+		return fmt.Errorf("HyperFleet API base URL not configured")
+	}
+
 	// Create HyperFleet API client from config
 	// The client is stateless and safe to reuse across messages.
 	// Each API call receives the message-specific context for proper isolation.
 	log.Info("Creating HyperFleet API client...")
 	apiClient, err := createAPIClient(adapterConfig.Spec.HyperfleetAPI)
 	if err != nil {
 		log.Error(fmt.Sprintf("Failed to create HyperFleet API client: %v", err))
 		return fmt.Errorf("failed to create HyperFleet API client: %w", err)
 	}
-
-	// Verify API base URL is configured
-	apiBaseURL := hyperfleet_api.BaseURLFromEnv()
-	if apiBaseURL == "" {
-		log.Error(fmt.Sprintf("HyperFleet API base URL not configured. Set %s environment variable", hyperfleet_api.EnvBaseURL))
-		return fmt.Errorf("HyperFleet API base URL not configured")
-	}
 	log.Infof("HyperFleet API client configured: baseURL=%s timeout=%s retryAttempts=%d",
 		apiBaseURL, adapterConfig.Spec.HyperfleetAPI.Timeout, adapterConfig.Spec.HyperfleetAPI.RetryAttempts)
pkg/errors/api_error.go (1)

142-153: Consider naming conventions for clarity.

The IsAPIError function name suggests a boolean return (like errors.Is), but it returns (*APIError, bool) similar to errors.As. While the docstring clarifies this, consider using only AsAPIError and removing IsAPIError to avoid potential confusion, or keep both but document that AsAPIError is preferred for idiomatic Go.

internal/config_loader/accessors.go (1)

1-18: Clean package-level helper for built-in variables.

The BuiltinVariables() function provides a clear list of always-available template variables. Consider documenting what each variable represents in the docstring for maintainability.

internal/config_loader/validator.go (2)

149-150: Consider expanding the template variable regex for edge cases.

The regex \{\{\s*\.([a-zA-Z_][a-zA-Z0-9_\.]*)\s*(?:\|[^}]*)?\}\} handles basic variables and pipes, but may miss some valid Go template constructs like {{ .foo | func "arg" }} with quoted arguments containing }.

For production robustness, consider using Go's text/template parser instead of regex for complex template validation. However, this regex covers the common cases adequately for now.


385-388: Intentionally ignoring CEL type-check errors.

The comment at line 386-387 explains that type checking may fail for dynamic variables. This is acceptable, but consider logging these issues at debug level for troubleshooting.

 	// Try to check the expression (type checking)
 	// Note: This may fail for dynamic variables, which is acceptable
-	_, _ = v.celEnv.Check(ast)
+	_, checkIssues := v.celEnv.Check(ast)
+	// Type check failures for dynamic types are expected and can be ignored
+	_ = checkIssues
internal/config_loader/validator_schema.go (2)

408-414: Consider path traversal validation.

The resolvePath function joins the baseDir with the refPath directly. While filepath.IsAbs prevents absolute paths from bypassing, relative paths like ../../../etc/passwd could potentially escape the intended directory.

Consider adding path traversal protection:

 func (v *SchemaValidator) resolvePath(refPath string) string {
 	if filepath.IsAbs(refPath) {
 		return refPath
 	}
-	return filepath.Join(v.baseDir, refPath)
+	resolved := filepath.Join(v.baseDir, refPath)
+	// Ensure resolved path is within baseDir
+	resolved = filepath.Clean(resolved)
+	return resolved
 }

For stricter security, also verify that the cleaned path starts with the baseDir prefix to prevent directory traversal attacks.


237-242: Non-deterministic HTTP method ordering in error message.

Iterating over a map produces non-deterministic order. This causes inconsistent error messages which can affect testing and user experience.

 	if _, valid := ValidHTTPMethods[apiCall.Method]; !valid {
 		methods := make([]string, 0, len(ValidHTTPMethods))
 		for m := range ValidHTTPMethods {
 			methods = append(methods, m)
 		}
+		sort.Strings(methods)
 		return fmt.Errorf("%s.method %q is invalid (allowed: %s)", path, apiCall.Method, strings.Join(methods, ", "))
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 987308f and 1680106.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (0 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_e2e_test.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • configs/adapter-config-template.yaml
✅ Files skipped from review due to trivial changes (1)
  • internal/hyperfleet_api/README.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • internal/hyperfleet_api/client_test.go
  • test/integration/config-loader/config_criteria_integration_test.go
  • test/testdata/templates/deployment.yaml
  • internal/criteria/evaluator_scenarios_test.go
  • internal/config_loader/README.md
  • internal/config_loader/loader_e2e_test.go
  • configs/templates/cluster-status-payload.yaml
  • internal/hyperfleet_api/types.go
  • internal/criteria/evaluator.go
🧰 Additional context used
🧬 Code graph analysis (12)
test/integration/k8s_client/helper_envtest_prebuilt.go (1)
test/integration/broker_consumer/testutil_container.go (1)
  • setupPubSubEmulatorContainer (30-73)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-61)
  • Resource (144-151)
  • Precondition (104-111)
  • PostAction (173-177)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/validator_test.go (8)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/config_loader/validator.go (1)
  • ValidationErrors (28-30)
pkg/errors/error.go (1)
  • Errors (77-95)
test/integration/k8s_client/client_integration_test.go (4)
  • TestIntegration_UpdateResource (179-222)
  • TestIntegration_ResourceLifecycle (270-328)
  • TestIntegration_DifferentResourceTypes (581-729)
  • TestIntegration_PatchResource (331-460)
pkg/errors/error_test.go (1)
  • TestNew (112-181)
pkg/logger/logger_test.go (2)
  • TestLoggerConstants (265-300)
  • TestLoggerMethods (196-237)
internal/k8s_client/types_test.go (1)
  • TestGVKFromKindAndApiVersion (9-116)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
test/integration/testutil/container.go (1)
test/integration/broker_consumer/testutil_container.go (1)
  • setupPubSubEmulatorContainer (30-73)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (114-122)
  • Resource (144-151)
  • SelectorConfig (161-164)
  • Parameter (47-61)
  • FetchExternalResource (96-101)
  • Precondition (104-111)
internal/config_loader/loader.go (3)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
internal/criteria/evaluator_test.go (2)
internal/criteria/types.go (6)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • EvaluationError (97-101)
internal/criteria/evaluator.go (5)
  • NewEvaluator (41-48)
  • ConditionDef (271-275)
  • EvaluationResult (10-21)
  • IsFieldNotFound (527-530)
  • FieldNotFoundError (516-520)
internal/config_loader/validator.go (3)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (114-122)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/config_loader/loader_test.go (3)
internal/config_loader/loader.go (6)
  • Load (85-108)
  • Parse (111-127)
  • LoadWithVersion (131-133)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (59-63)
internal/config_loader/types.go (10)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
  • AdapterConfig (4-9)
  • AdapterConfigSpec (19-27)
  • AdapterInfo (30-32)
  • Parameter (47-61)
  • Resource (144-151)
  • PostConfig (167-170)
  • DiscoveryConfig (154-158)
  • SelectorConfig (161-164)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (448-460)
  • IsSupportedAPIVersion (438-445)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (448-460)
internal/config_loader/validator.go (1)
  • Validate (462-464)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (32-49)
  • ConditionToCEL (199-241)
  • ConditionsToCEL (297-312)
internal/criteria/evaluator.go (2)
  • ConditionDef (271-275)
  • NewEvaluator (41-48)
🔇 Additional comments (62)
go.mod (3)

9-9: CEL library version update confirmed.

The dependency has been updated to github.com/google/cel-go v0.26.1, which is the latest stable version. This aligns with best practices and ensures the latest patches and fixes are included.


7-7: Based on the web search results, I can now evaluate the review comment's concern:

Key Finding: docker/go-connections and docker/docker are fundamentally different libraries:

  • docker/go-connections: Low-level networking utilities (socket helpers, dialers, TLS handling, URL parsing)
  • docker/docker (moby/moby): Full Docker Engine API client with high-level typed methods (ContainerList, ImagePull, ContainerCreate, etc.)

This means replacing docker/docker with docker/go-connections is not a compatible substitution. If the codebase uses any docker/docker client APIs (the Client type or its methods), this dependency change will cause compilation failures.

However, I cannot access the repository to verify whether such usage exists in the actual codebase. Without confirming the actual imports and usages, I cannot definitively validate or refute the review comment.


Verify Docker dependency migration does not break existing code.

The direct dependency on docker/docker has been replaced with docker/go-connections. These are not functionally equivalent: docker/go-connections provides only low-level networking utilities (sockets, dialers, TLS, URL helpers), while docker/docker is the full Docker Engine API client. If existing code uses docker/docker's Client type or high-level API methods (ContainerList, ImagePull, etc.), this change will cause build failures. Verify that the codebase does not depend on docker/docker APIs, or restore docker/docker as a dependency if needed.


15-15: Based on my verification attempts, I've encountered a limitation: the repository cannot be cloned to directly verify the actual content of go.mod. However, the web search results have revealed important information about the review comment's accuracy.

Key Finding: The review comment contains a factual error in its root cause analysis.

The review comment claims that go.yaml.in/yaml/v3 is "likely a typo" and should be gopkg.in/yaml.v3. However, web search results confirm that go.yaml.in/yaml/v3 is a legitimate, real Go module—it's the YAML organization's maintained fork/alternative version, distinct from the original gopkg.in/yaml.v3.

What I verified:

  • go.yaml.in/yaml/v3 exists as a real, maintained module (not a typo)
  • gopkg.in/yaml.v3 is a separate module with v3.0.1 available
  • v3.0.4 is not found in standard proxy listings for gopkg.in/yaml.v3
  • ❌ Cannot access the repository to verify actual go.mod lines 15 and 135
  • ❌ Cannot confirm if having both modules creates an actual conflict

What remains unverified:

  • Whether v3.0.4 exists for the go.yaml.in/yaml/v3 module
  • Actual content of lines 15 and 135 in go.mod
  • Whether module resolution actually conflicts
configs/README.md (1)

25-67: BROKER_SUBSCRIPTION_ID rename is consistent with the documented broker config

The example, env var table, and explanatory note now align on BROKER_SUBSCRIPTION_ID and clearly document its relationship to BROKER_GOOGLEPUBSUB_SUBSCRIPTION. No further adjustments needed here.

OWNERS (1)

1-16: Quoting numeric approver ID is a good YAML hygiene improvement

Quoting "86254860" avoids ambiguity with numeric literals in YAML parsers; the list otherwise remains unchanged.

internal/criteria/cel_evaluator.go (2)

31-62: Context‑driven CEL env construction looks solid

Building the CEL environment dynamically from EvaluationContext.Data with inferCELType is a reasonable approach and keeps the evaluator aligned with whatever the caller injects into the context. This should work well for typical map and primitive usage.


90-196: Evaluation pipeline and boolean handling are well‑structured

The parse → (best‑effort) check → program → eval flow, plus the empty‑expression shortcut to a true result, reads cleanly. The CELResult.Matched semantics (strict for bools, “non‑empty” for other values via isEmptyValue) also give a sensible default for callers that just care about “truthiness.”

internal/criteria/types.go (2)

7-58: Operator definitions and helper utilities are clear and complete

The Operator constants, SupportedOperators, IsValidOperator, and OperatorStrings provide a clean, discoverable surface for supported operators and centralize validation logic. This should make it easy to keep config validation and evaluator behavior in sync.


96-112: EvaluationError wrapper is well-structured for debugging

The error message includes both the field name and a human message, and Unwrap preserves the underlying error for callers that want to inspect or classify it. This should make criteria failures easy to trace.

internal/criteria/README.md (1)

1-381: Criteria README aligns well with the implemented API and operators

The README gives a clear picture of the supported operators, context usage, nested field access, and how this package integrates with the config loader. The examples line up with the Operator set and EvaluationContext/evaluator design in code, so this should be very helpful for future contributors and users.

configs/broker-configmap-pubsub-template.yaml (1)

24-29: Repository access unavailable for verification; concern about stale legacy keys remains unverified

The review recommends scanning for remaining uses of the legacy SUBSCRIPTION_ID and TOPIC keys to ensure the rename to BROKER_SUBSCRIPTION_ID and BROKER_TOPIC is complete. However, I was unable to execute verification scripts against the repository to confirm whether such stale references exist elsewhere in manifests, code, or documentation.

The renamed keys in the ConfigMap (lines 24–29) and the commented Deployment snippet (lines 93–98) appear internally consistent. However, the concern about lingering references to old SUBSCRIPTION_ID/TOPIC keys cannot be verified without repository access. This check should be performed locally to ensure no configuration mismatches exist in other files.

cmd/adapter/main.go (1)

187-220: LGTM!

The createAPIClient function properly validates timeout duration parsing, retry attempts, and backoff strategy with explicit allowed values. The switch statement for backoff strategy is clear and provides a helpful error message for invalid values.

internal/config_loader/validator_test.go (8)

12-120: LGTM!

The TestValidateConditionOperators test provides good coverage for operator validation scenarios including valid operators, invalid operators, and missing operator cases. The table-driven approach is well-structured.


122-268: LGTM!

Comprehensive coverage for template variable validation including defined variables, undefined variables in URLs and resource manifests, and nested variable access from stored responses. The tests correctly verify that the validator tracks variable scope throughout the configuration.


270-366: LGTM!

Good coverage for CEL expression validation including valid expressions, syntax errors, and usage of the has() function.


368-578: LGTM!

Comprehensive testing of Kubernetes manifest validation covering all required fields (apiVersion, kind, metadata, name) and manifest reference handling.


580-590: LGTM!

Good contract test ensuring all expected operators are supported by the criteria package. This helps catch regressions if operator support changes.


592-602: LGTM!

Good test for ValidationErrors aggregation and formatting.


604-646: LGTM!

Good integration test verifying that multiple validation errors are aggregated and reported together.


648-678: LGTM!

Good test verifying that builtin variables (like metadata.name) are properly recognized during template variable validation.

pkg/errors/api_error.go (3)

14-48: LGTM!

The APIError struct is well-designed with comprehensive context fields. The Error() method provides clear, informative messages with conditional formatting based on whether a status code is present, and Unwrap() correctly enables error chain support.


54-97: LGTM!

Comprehensive status code helpers covering common HTTP error scenarios. The IsTimeout() method correctly handles both HTTP 408 status and context.DeadlineExceeded.


103-132: LGTM!

Clean implementation of response body helpers and the constructor.

internal/criteria/evaluator_test.go (5)

11-111: LGTM!

Good coverage of EvaluationContext lifecycle including creation, set/get operations, nested field access, and context merging.


113-426: LGTM!

Comprehensive test coverage for all evaluation operators including edge cases like nil values, type mismatches, and empty collections.


428-629: LGTM!

Good integration tests for the Evaluator covering single conditions, multiple conditions (AND logic), and nested field access.


631-889: LGTM!

Thorough test coverage for helper functions including type conversion, error handling, field access with defaults, and evaluation results.


891-991: LGTM!

Excellent coverage of null handling scenarios including null parents, deeply nested null paths, and verification that safe access methods (GetFieldSafe, GetFieldOrDefault, HasField, EvaluateConditionSafe) behave correctly with null values.

internal/criteria/cel_evaluator_test.go (9)

10-18: LGTM!

Basic creation test for CELEvaluator verifying successful initialization with context data.


20-111: LGTM!

Comprehensive test coverage for CEL expression evaluation including string equality, numeric comparisons, boolean variables, compound expressions, list membership, empty expressions, and syntax error handling.


113-136: LGTM!

Good test for CEL expression evaluation with nested data structures.


138-173: LGTM!

Good coverage for typed evaluation helper methods EvaluateBool and EvaluateString.


175-260: LGTM!

Comprehensive test coverage for ConditionToCEL covering all operators and verifying the generated CEL expression format.


262-278: LGTM!

Good test for ConditionsToCEL verifying multiple conditions are joined with && and empty conditions return "true".


280-311: LGTM!

Good integration test verifying the Evaluator correctly delegates CEL evaluation methods.


313-330: LGTM!

Good test for CEL expression generation methods.


332-355: LGTM!

Good coverage for formatCELValue including edge cases like nil values, string escaping with quotes, and various numeric types.

internal/config_loader/loader_test.go (5)

1-10: LGTM! Well-structured test file with comprehensive coverage.

The imports are appropriate, and the use of testify/assert and testify/require follows Go testing best practices. The test organization is clean with table-driven tests for validation scenarios.


12-71: Good integration test for the Load function.

The test properly creates a temporary directory with t.TempDir() (which handles cleanup automatically), writes a complete valid config, and verifies all major fields after loading. This serves as a solid end-to-end test for the loader.


80-174: Solid table-driven tests for Parse validation.

The test cases cover essential validation scenarios including missing apiVersion, kind, metadata.name, and adapter.version. The pattern of checking both error presence and error message content ensures precise error reporting.


761-890: Thorough file reference validation tests.

The tests cover valid buildRef, missing files, directory references, manifest.ref, and post.params references. The edge cases including "is a directory" validation are particularly valuable.


1068-1263: Comprehensive resource discovery validation tests.

Good coverage of discovery requirements including namespace validation, byName/bySelectors alternatives, and empty selector validation. These tests ensure the loader enforces the discovery configuration correctly.

internal/config_loader/accessors.go (4)

31-71: Well-implemented variable collection with proper nil guard.

The GetDefinedVariables() method correctly aggregates variables from all sources (built-ins, params, preconditions, post params, resources). The nil check at line 34 prevents panics when called on nil receivers.


74-84: Correct pointer return pattern avoiding stale loop variable capture.

Using for i := range and returning &c.Spec.Params[i] is the correct idiom in Go to avoid returning a pointer to a loop variable. This pattern is consistently applied across all GetXxxByName methods.


167-174: Safe timeout parsing with empty string handling.

The ParseTimeout() method correctly returns (0, nil) for empty timeouts, allowing callers to use a default. This is a sensible API design choice documented in the comment.


194-221: Robust manifest ref extraction with defensive type handling.

The GetManifestRefs() method handles both single ref and refs array formats with proper type assertions. The empty string checks at lines 205 and 213 prevent invalid refs from being returned.

internal/config_loader/loader.go (4)

16-36: Good centralization of constants and valid HTTP methods.

The constants for API version and expected kind are well-placed. The ValidHTTPMethods map using empty structs is an efficient set implementation in Go.


85-108: Clean implementation of Load with auto base directory resolution.

The approach of prepending WithBaseDir(baseDir) before user options (line 106) correctly allows users to override the default. The use of filepath.Abs ensures consistent path resolution.


129-139: Deprecated wrappers maintained for backward compatibility.

The LoadWithVersion and ParseWithVersion functions are properly marked as deprecated with clear migration guidance in the comments. This is good API evolution practice.


148-195: Well-structured validation pipeline with clear ordering.

The pipeline correctly runs:

  1. Core structural validators (always)
  2. Adapter version validation (if version provided)
  3. File reference validation and loading (if baseDir set)
  4. Semantic validation (unless skipped)

This ordering ensures fail-fast behavior with proper error context.

internal/config_loader/validator.go (4)

17-49: Clean error aggregation implementation.

The ValidationError and ValidationErrors types provide good error context with paths. The formatted output at line 40 creates readable multi-error messages.


74-95: Well-orchestrated validation with proper initialization order.

The Validate() method correctly initializes context first (collecting parameters, creating CEL environment) before running validators. The nil check at line 75 is essential.


280-316: Dynamic CEL environment creation with deduplication.

The approach of extracting root variable names and tracking them in addedRoots prevents duplicate variable declarations. Using cel.DynType for unknown types at validation time is the correct approach.


422-453: Comprehensive K8s manifest validation.

The validation checks for required fields (apiVersion, kind, metadata) and validates that metadata.name is present. The empty string checks for apiVersion and kind are good defensive additions.

internal/config_loader/validator_schema.go (4)

16-29: Clean SchemaValidator design with optional baseDir.

The validator correctly separates concerns: ValidateStructure() for schema validation and ValidateFileReferences()/LoadFileReferences() for file operations. The baseDir check at lines 56 and 65 ensures file operations only run when appropriate.


303-325: File existence validation with clear error messages.

The validateFileExists method provides detailed error messages including the resolved path, which is helpful for debugging. The directory check at line 320 prevents accidental directory references.


331-390: Well-implemented file reference loading with proper error propagation.

The loadFileReferences method correctly:

  1. Handles both single and multiple refs
  2. Maintains backward compatibility by replacing Manifest for single refs
  3. Uses ManifestItems for multiple refs
  4. Provides clear error paths with indices

466-500: Lightweight backward compatibility wrappers.

The legacy functions create new SchemaValidator instances for each call. While this has minor overhead, it's acceptable for validation which runs infrequently. This approach keeps the API backward compatible while allowing the new structured validator to be used directly when preferred.

internal/hyperfleet_api/client.go (2)

341-402: Convenience HTTP verb helpers look clean and consistent

The Get/Post/Put/Patch/Delete helpers are thin, predictable wrappers that construct a Request, apply RequestOptions, and delegate to Do. The method signatures and behavior are uniform, which makes the client easy to use and test.

No changes needed here.


242-250: Based on the web search results, the technical claim in the review comment is valid: Go's context.WithTimeout(ctx, 0) does indeed create an immediately-expired context because it sets the deadline to time.Now().Add(0), causing the context's Done channel to close immediately and Err() to return context.DeadlineExceeded.

However, I cannot verify:

  1. Whether the code at lines 242-250 in internal/hyperfleet_api/client.go actually matches the provided snippet
  2. Whether the timeout variable can realistically be 0 in the codebase's execution paths
  3. Whether this is a currently-problematic condition or a theoretical concern

Verify the actual code state and determine if timeout can be zero in practice

The review comment's technical concern about context.WithTimeout with zero timeout is sound per Go documentation. However, without access to the codebase, I cannot confirm whether:

  • The code currently exhibits this pattern
  • The timeout parameter can actually reach zero during execution
  • This represents an actionable bug or a defensive code improvement

The suggested fix (checking if timeout > 0 before applying WithTimeout, otherwise using WithCancel) is the correct pattern per Go best practices.

test/testdata/adapter_config_valid.yaml (1)

1-202: Valid config fixture that exercises the new schema surface well

This YAML lines up cleanly with the new config types: top-level AdapterConfig, spec.hyperfleetApi, params (including env and event sources), structured preconditions (API call + extracts + conditions + CEL expression), rich resources (inline and manifest.ref), and post with build/conditions/data/observed_generation/lastUpdated plus a postActions API call.

It looks like a solid "golden" valid config to drive loader/validator and criteria tests.

internal/config_loader/types.go (1)

3-189: Schema definitions are cohesive and match the intended YAML surface

The struct graph (AdapterConfig, AdapterConfigSpec, HyperfleetAPIConfig, Parameter, ParameterBuild, Precondition, APICall, Resource, PostConfig, etc.) plus their YAML tags align with the adapter config you’re using in tests, including nested build/conditions/data and discovery fields. Internal-only fields like BuildRefContent and ManifestItems are correctly excluded from YAML via yaml:"-", which keeps the on-disk schema clean while giving the loader room to enrich configs.

No structural issues stand out in this type layer.

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: 2

♻️ Duplicate comments (2)
internal/criteria/cel_evaluator.go (2)

298-300: Same escaping issue in reflection path.

The reflection-based string handling has the same incomplete escaping as the direct string case above.

Apply the same fix here:

 		case reflect.String:
-			escaped := strings.ReplaceAll(rv.String(), `"`, `\"`)
-			return fmt.Sprintf(`"%s"`, escaped), nil
+			return strconv.Quote(rv.String()), nil

252-255: Incomplete string escaping may produce invalid CEL expressions.

The current implementation only escapes double quotes, but strings containing backslashes, newlines, tabs, or other control characters will produce invalid CEL literals. For example, a string containing \n would not be properly escaped.

Consider using Go's strconv.Quote which handles all necessary escape sequences:

 	case string:
-		// Escape quotes in string
-		escaped := strings.ReplaceAll(v, `"`, `\"`)
-		return fmt.Sprintf(`"%s"`, escaped), nil
+		// Use strconv.Quote for proper escaping of all special characters
+		return strconv.Quote(v), nil

Don't forget to add the import:

 import (
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"

This will properly handle backslashes, quotes, newlines, tabs, and other control characters.

🧹 Nitpick comments (6)
test/integration/testutil/container.go (2)

178-203: Consider the 60-second minimum cleanup timeout.

The enhanced cleanup logic with container ID capture and force cleanup fallback is well implemented. However, the minimum cleanup timeout of 60 seconds might be aggressive for test suites with many containers, as it could slow down overall test execution.

Consider making this configurable or using the original config.CleanupTimeout unless it's unexpectedly low (e.g., < 10s).

-			// Use longer timeout for cleanup to prevent stuck containers
-			cleanupTimeout := config.CleanupTimeout
-			if cleanupTimeout < 60*time.Second {
-				cleanupTimeout = 60 * time.Second
-			}
+			// Use longer timeout for cleanup to prevent stuck containers
+			cleanupTimeout := config.CleanupTimeout
+			if cleanupTimeout < 10*time.Second {
+				cleanupTimeout = 30 * time.Second
+			}

267-302: Consider using CombinedOutput for better diagnostics.

The CleanupLeakedContainers function uses listCmd.Output() (line 277), which only captures stdout. Errors written to stderr are silently ignored. While this is acceptable for best-effort cleanup, using CombinedOutput() would provide better diagnostics when the command fails.

 		// List containers matching the image
 		listCmd := exec.Command(runtime, "ps", "-a", "-q", "--filter", fmt.Sprintf("ancestor=%s", imagePattern))
-		output, err := listCmd.Output()
+		output, err := listCmd.CombinedOutput()
 		if err != nil {
+			t.Logf("Failed to list containers with %s: %v (output: %s)", runtime, err, string(output))
 			continue // Try next runtime
 		}
test/integration/k8s_client/helper_envtest_prebuilt.go (1)

136-147: Clarify the need for duplicate cleanup registration.

The explicit cleanup with setupSuccess flag adds a second cleanup path in addition to the t.Cleanup already registered by StartContainer. While this is safe due to the idempotent nature of Terminate, it adds complexity.

The benefit is that this deferred cleanup runs when the function returns (even on panic), while t.Cleanup runs after the test completes. However, the comment at line 137 says "immediate" which is slightly misleading since it's still deferred.

Consider adding a comment explaining why this second cleanup path is necessary despite StartContainer's cleanup registration, or simplify by relying solely on the t.Cleanup mechanism.

Example clarification:

 	// Register explicit cleanup for setup failure cases
-	// While StartContainer registers t.Cleanup, this ensures we catch setup failures immediately
-	// and fits the pattern of "cleanup before assertions"
+	// While StartContainer registers t.Cleanup (runs after test), this deferred cleanup
+	// ensures the container is terminated if this function panics or returns early,
+	// preventing resource leaks during setup failures

Also applies to: 186-186

configs/templates/job.yaml (1)

11-23: Job template looks good; consider TTL for completed Jobs

Spec and labels are consistent. To avoid orphaned Job/Pod objects piling up, you may want to set ttlSecondsAfterFinished on the Job spec as an optional hardening step.

 spec:
-  backoffLimit: 3
+  backoffLimit: 3
+  ttlSecondsAfterFinished: 3600
cmd/adapter/main.go (1)

138-161: Consider reducing or gating full event payload logging

log.Infof("Received event: ... data=%s", string(evt.Data())) logs the entire event body on every message, which can be noisy and may expose sensitive or high-volume payloads in production logs. Consider logging just metadata by default and moving full payloads behind a debug flag/level or truncating them.

-		log.Infof("Received event: id=%s type=%s source=%s data=%s", evt.ID(), evt.Type(), evt.Source(), string(evt.Data()))
+		log.Infof("Received event: id=%s type=%s source=%s", evt.ID(), evt.Type(), evt.Source())
+		// Optionally log full payload at a lower level or behind a flag:
+		// log.Debugf("Event payload: %s", string(evt.Data()))
internal/config_loader/validator_schema.go (1)

408-416: Minor: Redundant filepath.Clean call.

The comment correctly notes that filepath.Join already calls Clean internally, making the explicit Clean call redundant. While defensive, this could be simplified to just return filepath.Join(v.baseDir, refPath).

Apply this diff if you prefer to remove the redundancy:

-	// filepath.Join calls Clean, but we call it explicitly for clarity and to ensure
-	// the path is normalized (handling . and .. components)
-	return filepath.Clean(filepath.Join(v.baseDir, refPath))
+	// filepath.Join handles path normalization (. and .. components) via Clean
+	return filepath.Join(v.baseDir, refPath)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1680106 and dc4a921.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_e2e_test.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • configs/README.md
🚧 Files skipped from review as they are similar to previous changes (12)
  • configs/adapter-config-template.yaml
  • internal/criteria/README.md
  • test/testdata/templates/deployment.yaml
  • OWNERS
  • internal/hyperfleet_api/client_test.go
  • test/integration/k8s_client/helper_testcontainers_k3s.go
  • internal/config_loader/README.md
  • internal/config_loader/loader_e2e_test.go
  • internal/hyperfleet_api/client.go
  • pkg/errors/api_error.go
  • internal/config_loader/validator_test.go
  • internal/config_loader/loader_test.go
🧰 Additional context used
🧬 Code graph analysis (11)
test/integration/config-loader/config_criteria_integration_test.go (4)
internal/config_loader/loader.go (1)
  • Load (85-108)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorExists (26-26)
internal/criteria/evaluator.go (2)
  • NewEvaluator (41-48)
  • ConditionDef (271-275)
internal/criteria/cel_evaluator.go (2)
  • ConditionsToCEL (316-331)
  • ConditionToCEL (201-243)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-61)
  • Resource (144-151)
  • Precondition (104-111)
  • PostAction (173-177)
  • HyperfleetAPIConfig (35-39)
cmd/adapter/main.go (3)
internal/config_loader/loader.go (3)
  • EnvConfigPath (22-22)
  • Load (85-108)
  • WithAdapterVersion (52-56)
internal/config_loader/types.go (2)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
internal/hyperfleet_api/client.go (7)
  • BaseURLFromEnv (121-123)
  • EnvBaseURL (21-21)
  • ClientOption (36-36)
  • WithTimeout (65-69)
  • WithRetryAttempts (72-76)
  • WithRetryBackoff (79-83)
  • NewClient (100-118)
configs/broker-configmap-pubsub-template.yaml (1)
test/integration/broker_consumer/adapter_integration_test.go (1)
  • TestAdapterEnvironmentVariable (35-72)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (33-50)
  • ConditionToCEL (201-243)
  • ConditionsToCEL (316-331)
internal/criteria/evaluator.go (2)
  • ConditionDef (271-275)
  • NewEvaluator (41-48)
internal/config_loader/validator.go (3)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (114-122)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (114-122)
  • Resource (144-151)
  • SelectorConfig (161-164)
  • Parameter (47-61)
  • FetchExternalResource (96-101)
  • Precondition (104-111)
internal/config_loader/loader.go (3)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (450-462)
internal/config_loader/validator.go (1)
  • Validate (466-468)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (11)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • OperatorExists (26-26)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
internal/criteria/cel_evaluator.go (4)
  • CELResult (21-30)
  • NewCELEvaluator (33-50)
  • ConditionToCEL (201-243)
  • ConditionsToCEL (316-331)
🔇 Additional comments (21)
test/integration/testutil/container.go (2)

9-10: LGTM: Imports support new CLI-based cleanup.

The new imports are necessary for the docker/podman CLI-based force cleanup functionality.


156-169: Excellent leak prevention during retries.

The logic correctly handles the case where a container is created but fails to start (e.g., wait strategy timeout), ensuring it's terminated before retrying. The fallback to force cleanup if termination fails is a good defensive measure.

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

42-51: LGTM: Proper container cleanup.

The Cleanup method now properly terminates the container with an appropriate timeout. The idempotent behavior and non-failing error logging are appropriate for a cleanup function.

configs/templates/cluster-status-payload.yaml (1)

1-15: Cluster-status payload template looks consistent

Field naming, defaults, and the timestamp formatting all look reasonable for a status payload; the template is coherent with the surrounding config and templates.

configs/templates/deployment.yaml (1)

1-36: Deployment template is structurally sound

Selectors, labels, env wiring, and resource requests/limits are consistent and should work well with the adapter’s templating approach.

internal/hyperfleet_api/types.go (1)

24-232: HTTP client types and helpers look well-designed

The backoff strategy enum, default config, request/response models, and functional options form a clear and idiomatic surface, and the response helpers (including retryability) are coherent with the intended behavior.

test/testdata/adapter_config_valid.yaml (1)

16-201: Test adapter config comprehensively covers the new surface

This fixture exercises hyperfleetApi retry settings, params, preconditions, resources, and post/postActions in a realistic way, which should give good coverage for the loader and validator paths.

cmd/adapter/main.go (1)

62-91: Config loading and HyperFleet API client wiring look solid

The startup flow cleanly loads the adapter config (with adapter version), validates the base URL via env, and constructs a HyperFleet API client with parsed timeout and validated backoff strategy. The separation into createAPIClient keeps main() readable and will be easy to extend if more client options are added later.

Also applies to: 188-218

go.mod (1)

117-118: Remove the review comment; go.yaml.in/yaml/v3 is a valid and legitimate Go module.

The original comment incorrectly identifies go.yaml.in/yaml/v3 as a typo. Web search confirms this is the new official module path maintained by the YAML organization (github.com/yaml/go-yaml), whereas gopkg.in/yaml.v3 is the legacy frozen version. Both are legitimate but distinct modules representing different maintenance lines. Having both in go.mod is valid and does not break builds. The suggestion to remove the line is based on a false premise and should not be acted upon.

Likely an incorrect or invalid review comment.

internal/criteria/evaluator_scenarios_test.go (1)

1-300: LGTM! Comprehensive test coverage for realistic scenarios.

The test suite effectively validates the evaluator across multiple realistic scenarios including preconditions, resource status checks, nested field evaluation, terminating cluster handling, and node count validation. The tests are well-organized with clear naming and proper error handling.

internal/criteria/evaluator_test.go (1)

1-992: LGTM! Excellent test coverage for the evaluation engine.

The test suite comprehensively validates the evaluator API including context operations, all operators, field extraction utilities, nested field access, null handling, and CEL integration. The tests include proper error handling and cover important edge cases like null safety and deep nested paths.

configs/broker-configmap-pubsub-template.yaml (1)

24-28: LGTM! Configuration key renaming improves clarity.

The renaming of SUBSCRIPTION_ID to BROKER_SUBSCRIPTION_ID and TOPIC to BROKER_TOPIC makes the configuration more explicit and aligns with the test expectations in test/integration/broker_consumer/adapter_integration_test.go.

Also applies to: 93-97

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

1-428: LGTM! Comprehensive integration test coverage.

The integration tests effectively validate the end-to-end flow of config loading and criteria evaluation, including:

  • Precondition evaluation (individual and combined)
  • CEL expression conversion and evaluation
  • Handling of failing preconditions
  • Resource discovery field extraction
  • Post-processing with K8s resource data
  • Null safety with missing resources
  • Parameter extraction and validation

The previous issue with parameter source validation has been properly addressed using strings.HasPrefix.

internal/config_loader/accessors.go (1)

1-239: LGTM! Well-designed accessor methods with proper nil safety.

The accessor methods provide a clean API for querying adapter configuration data. The implementation includes:

  • Consistent nil checking across all methods
  • Support for both single and array manifest references
  • Proper type assertions with fallback semantics
  • Clear separation of concerns
internal/criteria/types.go (1)

1-113: LGTM! Well-structured type definitions with proper error handling.

The type definitions provide a solid foundation for the evaluation engine:

  • Eight operator constants with clear documentation
  • Helper functions for operator validation
  • EvaluationContext with proper initialization and data management
  • Error type following Go error wrapping conventions
internal/config_loader/loader.go (1)

1-195: LGTM! Well-designed config loader with flexible validation pipeline.

The loader implementation follows best practices:

  • Functional options pattern for flexible configuration
  • Auto-detection of config path from environment variable
  • Automatic baseDir derivation from config file path
  • Multi-stage validation pipeline with optional stages
  • Proper error handling and error context
internal/criteria/cel_evaluator_test.go (1)

1-367: Excellent test coverage!

This test suite is comprehensive and well-structured. The table-driven tests cover all the key scenarios: basic expressions, nested data access, operator conversions, edge cases, and integration flows. The test cases are clear and provide good documentation of expected behavior.

internal/config_loader/validator_schema.go (1)

16-502: Well-structured validation framework with clear separation of concerns.

The schema validator provides comprehensive structural validation, file reference checks, and YAML loading with appropriate error handling. The fail-fast validation pattern for structural checks and error accumulation for file references provide good user experience.

internal/config_loader/validator.go (1)

56-468: Comprehensive semantic validation with excellent error aggregation.

The validator performs thorough multi-stage validation covering operators, template variables (with nested path support), CEL expressions, and Kubernetes manifests. The error aggregation approach provides users with all validation issues at once, improving the developer experience. The CEL environment initialization correctly handles dynamic variables and the non-fatal type-check logging (Line 390) is appropriate for dynamic contexts.

internal/config_loader/types.go (1)

3-189: Well-designed configuration model with clear structure.

The type hierarchy provides a comprehensive and flexible configuration schema. The separation between inline/external content (e.g., BuildRef vs BuildRefContent), support for multiple parameter sources, and the discovery pattern for resources demonstrate thoughtful design. All types follow Go conventions and use appropriate YAML tags.

internal/criteria/evaluator.go (1)

9-531: Robust and comprehensive criteria evaluation engine.

The evaluator provides a complete evaluation framework with excellent design choices:

  • Multiple field access variants (safe/unsafe) for different use cases
  • Detailed result types that capture evaluation context
  • Comprehensive operator support with proper type handling
  • Nested field traversal supporting both maps and structs (with case-insensitive matching)
  • Clear error types (FieldNotFoundError) with path information
  • Seamless CEL integration for advanced expression evaluation

The operator implementations are thorough, handling edge cases like nil values, type conversions, and collection operations correctly.

@xueli181114
Copy link
Contributor Author

Local testing result of integration ``` xueli@cloudshell:~/hyperfleet-adapter (hcm-hyperfleet)$ git branch * HYPERFLEET-47 HYPERFLEET-49 main xueli@cloudshell:~/hyperfleet-adapter (hcm-hyperfleet)$ make test-integration^C xueli@cloudshell:~/hyperfleet-adapter (hcm-hyperfleet)$ nano ~/.docker/config.json xueli@cloudshell:~/hyperfleet-adapter (hcm-hyperfleet)$ mv ~/.docker/config.json ~/.docker/config.bk xueli@cloudshell:~/hyperfleet-adapter (hcm-hyperfleet)$ make test-integration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🐳 Running Integration Tests with Testcontainers ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✅ Container runtime: docker

🚀 Starting integration tests...
Checking integration image configuration...
INTEGRATION_ENVTEST_IMAGE not set, using local image
⚠️ Local integration image not found. Building it...

make[1]: Entering directory '/home/xueli/hyperfleet-adapter'
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔨 Building Integration Test Image
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📦 Building image: localhost/hyperfleet-integration-test:latest
This downloads ~100MB of Kubernetes binaries (one-time operation)

[+] Building 132.7s (14/14) FINISHED docker:default
=> [internal] load build definition from Dockerfile.integration 0.0s
=> => transferring dockerfile: 1.93kB 0.0s
=> [internal] load metadata for docker.io/library/golang:1.25-alpine 3.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 606B 0.0s
=> [1/9] FROM docker.io/library/golang:1.25-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb 7.8s
=> => resolve docker.io/library/golang:1.25-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb 0.0s
=> => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B 0.4s
=> => sha256:ae96bccb6682de86f077a4ef76a3024218e06114ae177db9ad565f2be5a0e423 126B / 126B 0.4s
=> => sha256:7c9d4a4eea0de466b378fec1876ea74acd9465fc6a1d15368a117eeacaa21b7d 60.15MB / 60.15MB 1.6s
=> => sha256:8005175e490b0ff92097d506946bbecbb38ca5479503236dcb3350f2da29b1cb 291.16kB / 291.16kB 0.5s
=> => sha256:2d35ebdb57d9971fea0cac1582aa78935adf8058b2cc32db163c98822e5dfa1b 3.80MB / 3.80MB 0.5s
=> => extracting sha256:2d35ebdb57d9971fea0cac1582aa78935adf8058b2cc32db163c98822e5dfa1b 0.3s
=> => extracting sha256:8005175e490b0ff92097d506946bbecbb38ca5479503236dcb3350f2da29b1cb 0.1s
=> => extracting sha256:7c9d4a4eea0de466b378fec1876ea74acd9465fc6a1d15368a117eeacaa21b7d 6.0s
=> => extracting sha256:ae96bccb6682de86f077a4ef76a3024218e06114ae177db9ad565f2be5a0e423 0.0s
=> => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 0.0s
=> [internal] load build context 0.1s
=> => transferring context: 1.45kB 0.0s
=> [2/9] RUN apk add --no-cache curl tar openssl bash 4.3s
=> [3/9] RUN go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 67.3s
=> [4/9] RUN /go/bin/setup-envtest use 1.30.x --bin-dir /usr/local/kubebuilder -p path > /tmp/envtest-path && cat /tmp/envtest-path && ENVTEST_PATH=$(cat /tmp/envtest-path) && ln -sf ${ENVTEST_PATH}/etcd /usr/local/bin/et 17.1s
=> [5/9] RUN mkdir -p /tmp/envtest/etcd /tmp/envtest/certs 0.3s
=> [6/9] RUN openssl genrsa -out /tmp/envtest/certs/sa.key 2048 && openssl rsa -in /tmp/envtest/certs/sa.key -pubout -out /tmp/envtest/certs/sa.pub 0.5s
=> [7/9] RUN echo "test-token,test-user,test-uid,system:masters" > /tmp/envtest/certs/token-auth-file 0.4s
=> [8/9] COPY scripts/start-envtest.sh /usr/local/bin/start-envtest.sh 0.1s
=> [9/9] RUN chmod +x /usr/local/bin/start-envtest.sh 0.4s
=> exporting to image 31.2s
=> => exporting layers 25.9s
=> => exporting manifest sha256:8f173a4689e3911ee48c1d4038faf66f10c42c17a2217603574c9fde6d08198c 0.0s
=> => exporting config sha256:45086b2c63994f882d95ccbea77c60fff39228a75a9108da97698599181a3bfa 0.0s
=> => exporting attestation manifest sha256:92955dbff5567ae550161587b19340e3641033abab4187823d75a014d92915ea 0.0s
=> => exporting manifest list sha256:dc6361b2451c4307989db12d97e6237983a2dd1f3d1d73dac3277930d16c93fe 0.0s
=> => naming to localhost/hyperfleet-integration-test:latest 0.0s
=> => unpacking to localhost/hyperfleet-integration-test:latest 5.2s

✅ Integration test image built successfully!
Image: localhost/hyperfleet-integration-test:latest

make[1]: Leaving directory '/home/xueli/hyperfleet-adapter'

Using INTEGRATION_ENVTEST_IMAGE=localhost/hyperfleet-integration-test:latest

2025/11/28 07:56:14 github.com/testcontainers/testcontainers-go - Connected to docker:
Server Version: 29.0.2
API Version: 1.51
Operating System: Ubuntu 24.04.3 LTS (containerized)
Total Memory: 7947 MB
Testcontainers for Go Version: v0.40.0
Resolved Docker Host: unix:///var/run/docker.sock
Resolved Docker Socket Path: /var/run/docker.sock
Test SessionID: 82c8b574467739f511f56499c116b963355283d6bfa38a54635249c396993c32
Test ProcessID: ceffcccb-80a9-40e3-9cf8-2451a9062f57
✅ Container runtime available: localhost
📦 Note: First run will download Pub/Sub emulator image (~2GB)
This may take several minutes depending on your internet connection

=== RUN TestAdapterEnvironmentVariable
testutil_container.go:31: ========================================
testutil_container.go:32: Starting Google Pub/Sub emulator container...
testutil_container.go:33: Note: First run will download ~2GB image (this may take several minutes)
testutil_container.go:34: ========================================
testutil_container.go:59: Pulling/starting container (this may take a while on first run)...
testutil_container.go:60: Starting Pub/Sub emulator container (image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators)...
2025/11/28 07:56:14 No image auth found for gcr.io. Setting empty credentials for the image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators. This is expected for public images. Details: credentials not found in native keychain
2025/11/28 07:57:11 🐳 Creating container for image gcr.io/google.com/cloudsdktool/cloud-sdk:emulators
2025/11/28 07:57:12 ⏳ Waiting for Reaper "1f8c209a" to be ready
2025/11/28 07:57:12 🔥 Reaper obtained from Docker for this test session 1f8c209a
2025/11/28 07:57:12 ✅ Container created: b04dc899b3fe
2025/11/28 07:57:12 🐳 Starting container: b04dc899b3fe
2025/11/28 07:57:13 ✅ Container started: b04dc899b3fe
2025/11/28 07:57:13 ⏳ Waiting for container id b04dc899b3fe image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators. Waiting for: all of: [log message "[pubsub] This is the Google Pub/Sub fake.", port 8085/tcp to be listening]
2025/11/28 07:57:20 🔔 Container is ready: b04dc899b3fe
testutil_container.go:60: Pub/Sub emulator container started successfully (host: localhost)
testutil_container.go:64: Pub/Sub emulator started: localhost:32770 (project: test-project)
=== RUN TestAdapterEnvironmentVariable/uses_BROKER_SUBSCRIPTION_ID_when_set
I1128 07:57:20.210331 2922 consumer.go:75] Creating subscriber for subscription: broker-sub-id-test
I1128 07:57:20.212800 2922 consumer.go:85] Broker subscriber created successfully
=== RUN TestAdapterEnvironmentVariable/returns_error_when_BROKER_SUBSCRIPTION_ID_is_not_set
=== NAME TestAdapterEnvironmentVariable
container.go:183: Stopping and removing Pub/Sub emulator container (ID: b04dc899b3fe3aba2b27dc413e8b9511ed7193dea1cc492d27b0f5f1417355f4)...
2025/11/28 07:57:20 🐳 Stopping container: b04dc899b3fe
2025/11/28 07:57:30 ✅ Container stopped: b04dc899b3fe
2025/11/28 07:57:30 🐳 Terminating container: b04dc899b3fe
2025/11/28 07:57:30 🚫 Container terminated: b04dc899b3fe
container.go:202: Pub/Sub emulator container stopped and removed successfully
--- PASS: TestAdapterEnvironmentVariable (76.29s)
--- PASS: TestAdapterEnvironmentVariable/uses_BROKER_SUBSCRIPTION_ID_when_set (0.00s)
--- PASS: TestAdapterEnvironmentVariable/returns_error_when_BROKER_SUBSCRIPTION_ID_is_not_set (0.00s)
=== RUN TestAdapterSmokeTest
testutil_container.go:31: ========================================
testutil_container.go:32: Starting Google Pub/Sub emulator container...
testutil_container.go:33: Note: First run will download ~2GB image (this may take several minutes)
testutil_container.go:34: ========================================
testutil_container.go:59: Pulling/starting container (this may take a while on first run)...
testutil_container.go:60: Starting Pub/Sub emulator container (image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators)...
2025/11/28 07:57:30 🐳 Creating container for image gcr.io/google.com/cloudsdktool/cloud-sdk:emulators
2025/11/28 07:57:30 ✅ Container created: 474a1b0cf625
2025/11/28 07:57:30 🐳 Starting container: 474a1b0cf625
2025/11/28 07:57:31 ✅ Container started: 474a1b0cf625
2025/11/28 07:57:31 ⏳ Waiting for container id 474a1b0cf625 image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators. Waiting for: all of: [log message "[pubsub] This is the Google Pub/Sub fake.", port 8085/tcp to be listening]
2025/11/28 07:57:37 🔔 Container is ready: 474a1b0cf625
testutil_container.go:60: Pub/Sub emulator container started successfully (host: localhost)
testutil_container.go:64: Pub/Sub emulator started: localhost:32772 (project: test-project)
I1128 07:57:37.697524 2922 consumer.go:75] Creating subscriber for subscription: adapter-smoke-test
I1128 07:57:37.699855 2922 consumer.go:85] Broker subscriber created successfully
I1128 07:57:37.699930 2922 consumer.go:103] Subscribing to topic: smoke-test-topic
[watermill] 2025/11/28 07:57:37.700005 router.go:280: level=INFO msg="Adding handler" handler_name=smoke-test-topic-0 topic=smoke-test-topic
[watermill] 2025/11/28 07:57:37.700160 router.go:434: level=INFO msg="Running router handlers" count=1
[watermill] 2025/11/28 07:57:37.700197 subscriber.go:184: level=INFO msg="Subscribing to Google Cloud PubSub topic" provider=google_cloud_pubsub subscription_name=smoke-test-topic-adapter-smoke-test topic=smoke-test-topic
I1128 07:57:37.700121 2922 consumer.go:114] Successfully subscribed to topic: smoke-test-topic
adapter_integration_test.go:122: Publishing 1 test messages to topic: smoke-test-topic
[watermill] 2025/11/28 07:57:39.245344 router.go:648: level=INFO msg="Starting handler" subscriber_name=smoke-test-topic-0 topic=smoke-test-topic
adapter_integration_test.go:122: Published message 0: id=c7dcd0fb-da53-4b1b-b352-5155cb149d06
adapter_integration_test.go:104: Smoke test received message: c7dcd0fb-da53-4b1b-b352-5155cb149d06
adapter_integration_test.go:122: Successfully published 1 messages
[watermill] 2025/11/28 07:57:39.432859 publisher.go:211: level=INFO msg="Closing Google PubSub publisher"
[watermill] 2025/11/28 07:57:39.433396 publisher.go:225: level=INFO msg="Google PubSub publisher closed"
adapter_integration_test.go:127: Message received successfully
[watermill] 2025/11/28 07:57:39.433561 subscriber.go:203: level=INFO msg="Receiving messages finished with no error" provider=google_cloud_pubsub subscription_name=smoke-test-topic-adapter-smoke-test topic=smoke-test-topic
[watermill] 2025/11/28 07:57:39.433622 router.go:483: level=INFO msg="Subscriber stopped" subscriber_name=smoke-test-topic-0 topic=smoke-test-topic
[watermill] 2025/11/28 07:57:39.433841 router.go:529: level=ERROR msg="All handlers stopped, closing router" err="all router handlers stopped"
[watermill] 2025/11/28 07:57:39.433862 router.go:580: level=INFO msg="Closing router"
[watermill] 2025/11/28 07:57:39.433928 router.go:413: level=INFO msg="Waiting for messages" timeout=30s
[watermill] 2025/11/28 07:57:39.433971 router.go:591: level=INFO msg="Router closed"
[watermill] 2025/11/28 07:57:39.433991 router.go:419: level=INFO msg="All messages processed"
container.go:183: Stopping and removing Pub/Sub emulator container (ID: 474a1b0cf6254ad87b3093aea4659b34f59f02c8caed136b43638da9de50144f)...
2025/11/28 07:57:39 🐳 Stopping container: 474a1b0cf625
2025/11/28 07:57:49 ✅ Container stopped: 474a1b0cf625
2025/11/28 07:57:49 🐳 Terminating container: 474a1b0cf625
2025/11/28 07:57:49 🚫 Container terminated: 474a1b0cf625
container.go:202: Pub/Sub emulator container stopped and removed successfully
--- PASS: TestAdapterSmokeTest (19.22s)
=== RUN TestAdapterConcurrentSubscribers
testutil_container.go:31: ========================================
testutil_container.go:32: Starting Google Pub/Sub emulator container...
testutil_container.go:33: Note: First run will download ~2GB image (this may take several minutes)
testutil_container.go:34: ========================================
testutil_container.go:59: Pulling/starting container (this may take a while on first run)...
testutil_container.go:60: Starting Pub/Sub emulator container (image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators)...
2025/11/28 07:57:49 🐳 Creating container for image gcr.io/google.com/cloudsdktool/cloud-sdk:emulators
2025/11/28 07:57:49 ✅ Container created: 5fd1f595b6a7
2025/11/28 07:57:49 🐳 Starting container: 5fd1f595b6a7
2025/11/28 07:57:50 ✅ Container started: 5fd1f595b6a7
2025/11/28 07:57:50 ⏳ Waiting for container id 5fd1f595b6a7 image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators. Waiting for: all of: [log message "[pubsub] This is the Google Pub/Sub fake.", port 8085/tcp to be listening]
2025/11/28 07:57:57 🔔 Container is ready: 5fd1f595b6a7
testutil_container.go:60: Pub/Sub emulator container started successfully (host: localhost)
testutil_container.go:64: Pub/Sub emulator started: localhost:32774 (project: test-project)
adapter_integration_test.go:179: Goroutine 4: Creating subscriber
I1128 07:57:57.498930 2922 consumer.go:75] Creating subscriber for subscription: concurrent-test
adapter_integration_test.go:179: Goroutine 0: Creating subscriber
I1128 07:57:57.499033 2922 consumer.go:75] Creating subscriber for subscription: concurrent-test
I1128 07:57:57.499404 2922 consumer.go:85] Broker subscriber created successfully
adapter_integration_test.go:187: Goroutine 0: Subscribing to topic
I1128 07:57:57.499438 2922 consumer.go:103] Subscribing to topic: concurrent-test-topic
[watermill] 2025/11/28 07:57:57.499459 router.go:280: level=INFO msg="Adding handler" handler_name=concurrent-test-topic-0 topic=concurrent-test-topic
I1128 07:57:57.499484 2922 consumer.go:114] Successfully subscribed to topic: concurrent-test-topic
adapter_integration_test.go:199: Goroutine 0: Success
[watermill] 2025/11/28 07:57:57.499518 router.go:434: level=INFO msg="Running router handlers" count=1
[watermill] 2025/11/28 07:57:57.499539 subscriber.go:184: level=INFO msg="Subscribing to Google Cloud PubSub topic" provider=google_cloud_pubsub subscription_name=concurrent-test-topic-concurrent-test topic=concurrent-test-topic
I1128 07:57:57.500339 2922 consumer.go:85] Broker subscriber created successfully
adapter_integration_test.go:187: Goroutine 4: Subscribing to topic
I1128 07:57:57.500377 2922 consumer.go:103] Subscribing to topic: concurrent-test-topic
[watermill] 2025/11/28 07:57:57.500394 router.go:280: level=INFO msg="Adding handler" handler_name=concurrent-test-topic-0 topic=concurrent-test-topic
I1128 07:57:57.500412 2922 consumer.go:114] Successfully subscribed to topic: concurrent-test-topic
adapter_integration_test.go:199: Goroutine 4: Success
[watermill] 2025/11/28 07:57:57.500444 router.go:434: level=INFO msg="Running router handlers" count=1
[watermill] 2025/11/28 07:57:57.500462 subscriber.go:184: level=INFO msg="Subscribing to Google Cloud PubSub topic" provider=google_cloud_pubsub subscription_name=concurrent-test-topic-concurrent-test topic=concurrent-test-topic
adapter_integration_test.go:179: Goroutine 1: Creating subscriber
I1128 07:57:57.500586 2922 consumer.go:75] Creating subscriber for subscription: concurrent-test
I1128 07:57:57.500796 2922 consumer.go:85] Broker subscriber created successfully
adapter_integration_test.go:187: Goroutine 1: Subscribing to topic
I1128 07:57:57.500820 2922 consumer.go:103] Subscribing to topic: concurrent-test-topic
[watermill] 2025/11/28 07:57:57.500858 router.go:280: level=INFO msg="Adding handler" handler_name=concurrent-test-topic-0 topic=concurrent-test-topic
I1128 07:57:57.500873 2922 consumer.go:114] Successfully subscribed to topic: concurrent-test-topic
adapter_integration_test.go:199: Goroutine 1: Success
[watermill] 2025/11/28 07:57:57.500908 router.go:434: level=INFO msg="Running router handlers" count=1
[watermill] 2025/11/28 07:57:57.500926 subscriber.go:184: level=INFO msg="Subscribing to Google Cloud PubSub topic" provider=google_cloud_pubsub subscription_name=concurrent-test-topic-concurrent-test topic=concurrent-test-topic
adapter_integration_test.go:179: Goroutine 2: Creating subscriber
I1128 07:57:57.501087 2922 consumer.go:75] Creating subscriber for subscription: concurrent-test
adapter_integration_test.go:179: Goroutine 3: Creating subscriber
I1128 07:57:57.502167 2922 consumer.go:75] Creating subscriber for subscription: concurrent-test
I1128 07:57:57.502425 2922 consumer.go:85] Broker subscriber created successfully
adapter_integration_test.go:187: Goroutine 3: Subscribing to topic
I1128 07:57:57.502466 2922 consumer.go:103] Subscribing to topic: concurrent-test-topic
[watermill] 2025/11/28 07:57:57.502484 router.go:280: level=INFO msg="Adding handler" handler_name=concurrent-test-topic-0 topic=concurrent-test-topic
I1128 07:57:57.502505 2922 consumer.go:114] Successfully subscribed to topic: concurrent-test-topic
adapter_integration_test.go:199: Goroutine 3: Success
[watermill] 2025/11/28 07:57:57.502552 router.go:434: level=INFO msg="Running router handlers" count=1
[watermill] 2025/11/28 07:57:57.502579 subscriber.go:184: level=INFO msg="Subscribing to Google Cloud PubSub topic" provider=google_cloud_pubsub subscription_name=concurrent-test-topic-concurrent-test topic=concurrent-test-topic
I1128 07:57:57.505504 2922 consumer.go:85] Broker subscriber created successfully
adapter_integration_test.go:187: Goroutine 2: Subscribing to topic
I1128 07:57:57.505543 2922 consumer.go:103] Subscribing to topic: concurrent-test-topic
[watermill] 2025/11/28 07:57:57.505562 router.go:280: level=INFO msg="Adding handler" handler_name=concurrent-test-topic-0 topic=concurrent-test-topic
I1128 07:57:57.505577 2922 consumer.go:114] Successfully subscribed to topic: concurrent-test-topic
adapter_integration_test.go:199: Goroutine 2: Success
adapter_integration_test.go:236: Successfully created 5 concurrent subscribers with same subscription ID
[watermill] 2025/11/28 07:57:57.505671 router.go:434: level=INFO msg="Running router handlers" count=1
[watermill] 2025/11/28 07:57:57.506142 subscriber.go:184: level=INFO msg="Subscribing to Google Cloud PubSub topic" provider=google_cloud_pubsub subscription_name=concurrent-test-topic-concurrent-test topic=concurrent-test-topic
[watermill] 2025/11/28 07:57:57.508027 subscriber.go:84: level=ERROR msg="Router stopped with error" err="cannot subscribe topic concurrent-test-topic: could not get subscription concurrent-test-topic-concurrent-test: rpc error: code = Canceled desc = grpc: the client connection is closing" subscription_id=concurrent-test topic=concurrent-test-topic
[watermill] 2025/11/28 07:57:57.509365 subscriber.go:84: level=ERROR msg="Router stopped with error" err="cannot subscribe topic concurrent-test-topic: could not get subscription concurrent-test-topic-concurrent-test: rpc error: code = Canceled desc = grpc: the client connection is closing" subscription_id=concurrent-test topic=concurrent-test-topic
[watermill] 2025/11/28 07:57:57.509888 subscriber.go:84: level=ERROR msg="Router stopped with error" err="cannot subscribe topic concurrent-test-topic: could not get subscription concurrent-test-topic-concurrent-test: rpc error: code = Canceled desc = grpc: the client connection is closing" subscription_id=concurrent-test topic=concurrent-test-topic
[watermill] 2025/11/28 07:57:57.511141 subscriber.go:84: level=ERROR msg="Router stopped with error" err="cannot subscribe topic concurrent-test-topic: could not get subscription concurrent-test-topic-concurrent-test: rpc error: code = Canceled desc = grpc: the client connection is closing" subscription_id=concurrent-test topic=concurrent-test-topic
[watermill] 2025/11/28 07:57:57.511579 subscriber.go:84: level=ERROR msg="Router stopped with error" err="cannot subscribe topic concurrent-test-topic: could not get subscription concurrent-test-topic-concurrent-test: rpc error: code = Canceled desc = grpc: the client connection is closing" subscription_id=concurrent-test topic=concurrent-test-topic
container.go:183: Stopping and removing Pub/Sub emulator container (ID: 5fd1f595b6a7a2309b89237b42409f441b6b457f1c62a0dce7d7714b8467330b)...
2025/11/28 07:57:57 🐳 Stopping container: 5fd1f595b6a7
2025/11/28 07:58:07 ✅ Container stopped: 5fd1f595b6a7
2025/11/28 07:58:07 🐳 Terminating container: 5fd1f595b6a7
2025/11/28 07:58:07 🚫 Container terminated: 5fd1f595b6a7
container.go:202: Pub/Sub emulator container stopped and removed successfully
--- PASS: TestAdapterConcurrentSubscribers (18.13s)
PASS
ok github.com/openshift-hyperfleet/hyperfleet-adapter/test/integration/broker_consumer 114.019s
=== RUN TestConfigLoadAndCriteriaEvaluation
=== RUN TestConfigLoadAndCriteriaEvaluation/evaluate_precondition_conditions_from_config
config_criteria_integration_test.go:59: Evaluating condition 0: clusterPhase in [Provisioning Installing Ready]
config_criteria_integration_test.go:59: Evaluating condition 1: cloudProvider in [aws gcp azure]
config_criteria_integration_test.go:59: Evaluating condition 2: vpcId exists
=== RUN TestConfigLoadAndCriteriaEvaluation/evaluate_all_preconditions_as_combined_expression
config_criteria_integration_test.go:94: Extracted fields: map[cloudProvider:aws clusterPhase:Ready vpcId:vpc-12345]
--- PASS: TestConfigLoadAndCriteriaEvaluation (0.08s)
--- PASS: TestConfigLoadAndCriteriaEvaluation/evaluate_precondition_conditions_from_config (0.00s)
--- PASS: TestConfigLoadAndCriteriaEvaluation/evaluate_all_preconditions_as_combined_expression (0.00s)
=== RUN TestConfigConditionsToCEL
=== RUN TestConfigConditionsToCEL/convert_conditions_to_CEL
config_criteria_integration_test.go:120: Generated CEL expression: (clusterPhase in ["Provisioning", "Installing", "Ready"]) && (cloudProvider in ["aws", "gcp", "azure"]) && ((vpcId != null && vpcId != ""))
config_criteria_integration_test.go:126: Condition 0 CEL: clusterPhase in ["Provisioning", "Installing", "Ready"]
config_criteria_integration_test.go:126: Condition 1 CEL: cloudProvider in ["aws", "gcp", "azure"]
config_criteria_integration_test.go:126: Condition 2 CEL: (vpcId != null && vpcId != "")
=== RUN TestConfigConditionsToCEL/evaluate_converted_CEL_expression
--- PASS: TestConfigConditionsToCEL (0.02s)
--- PASS: TestConfigConditionsToCEL/convert_conditions_to_CEL (0.00s)
--- PASS: TestConfigConditionsToCEL/evaluate_converted_CEL_expression (0.00s)
=== RUN TestConfigWithFailingPreconditions
=== RUN TestConfigWithFailingPreconditions/preconditions_fail_with_wrong_phase
=== RUN TestConfigWithFailingPreconditions/preconditions_fail_with_wrong_provider
=== RUN TestConfigWithFailingPreconditions/preconditions_fail_with_missing_vpcId
--- PASS: TestConfigWithFailingPreconditions (0.01s)
--- PASS: TestConfigWithFailingPreconditions/preconditions_fail_with_wrong_phase (0.00s)
--- PASS: TestConfigWithFailingPreconditions/preconditions_fail_with_wrong_provider (0.00s)
--- PASS: TestConfigWithFailingPreconditions/preconditions_fail_with_missing_vpcId (0.00s)
=== RUN TestConfigResourceDiscoveryFields
=== RUN TestConfigResourceDiscoveryFields/verify_resource_discovery_configs
config_criteria_integration_test.go:228: Resource: clusterNamespace
config_criteria_integration_test.go:231: Discovery namespace: cluster-{{ .clusterId }}
config_criteria_integration_test.go:232: Discovery byName:
config_criteria_integration_test.go:234: Discovery selectors: map[hyperfleet.io/cluster-id:{{ .clusterId }} hyperfleet.io/managed-by:{{ .metadata.name }} hyperfleet.io/resource-type:namespace]
config_criteria_integration_test.go:228: Resource: clusterConfigMap
config_criteria_integration_test.go:231: Discovery namespace: cluster-{{ .clusterId }}
config_criteria_integration_test.go:232: Discovery byName:
config_criteria_integration_test.go:234: Discovery selectors: map[hyperfleet.io/cluster-id:{{ .clusterId }} hyperfleet.io/resource-type:configmap]
config_criteria_integration_test.go:228: Resource: clusterSecret
config_criteria_integration_test.go:231: Discovery namespace: cluster-{{ .clusterId }}
config_criteria_integration_test.go:232: Discovery byName: cluster-credentials-{{ .clusterId }}
config_criteria_integration_test.go:228: Resource: validationJob
config_criteria_integration_test.go:231: Discovery namespace: cluster-{{ .clusterId }}
config_criteria_integration_test.go:232: Discovery byName:
config_criteria_integration_test.go:234: Discovery selectors: map[hyperfleet.io/cluster-id:{{ .clusterId }} hyperfleet.io/managed-by:{{ .metadata.name }} hyperfleet.io/resource-type:job]
config_criteria_integration_test.go:228: Resource: clusterController
config_criteria_integration_test.go:231: Discovery namespace: cluster-{{ .clusterId }}
config_criteria_integration_test.go:232: Discovery byName:
config_criteria_integration_test.go:234: Discovery selectors: map[hyperfleet.io/cluster-id:{{ .clusterId }} hyperfleet.io/managed-by:{{ .metadata.name }} hyperfleet.io/resource-type:controller]
--- PASS: TestConfigResourceDiscoveryFields (0.01s)
--- PASS: TestConfigResourceDiscoveryFields/verify_resource_discovery_configs (0.00s)
=== RUN TestConfigPostProcessingEvaluation
=== RUN TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data
=== RUN TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/access_namespace_status
=== RUN TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/access_deployment_replicas
=== RUN TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/evaluate_deployment_ready_condition
=== RUN TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/evaluate_replicas_match
=== RUN TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/evaluate_with_CEL_expression
--- PASS: TestConfigPostProcessingEvaluation (0.02s)
--- PASS: TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data (0.00s)
--- PASS: TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/access_namespace_status (0.00s)
--- PASS: TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/access_deployment_replicas (0.00s)
--- PASS: TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/evaluate_deployment_ready_condition (0.00s)
--- PASS: TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/evaluate_replicas_match (0.00s)
--- PASS: TestConfigPostProcessingEvaluation/simulate_post-processing_with_k8s_resource_data/evaluate_with_CEL_expression (0.00s)
=== RUN TestConfigNullSafetyWithMissingResources
=== RUN TestConfigNullSafetyWithMissingResources/handle_missing_resource_gracefully
=== RUN TestConfigNullSafetyWithMissingResources/handle_deeply_nested_null
--- PASS: TestConfigNullSafetyWithMissingResources (0.01s)
--- PASS: TestConfigNullSafetyWithMissingResources/handle_missing_resource_gracefully (0.00s)
--- PASS: TestConfigNullSafetyWithMissingResources/handle_deeply_nested_null (0.00s)
=== RUN TestConfigParameterExtraction
=== RUN TestConfigParameterExtraction/verify_required_parameters
config_criteria_integration_test.go:410: Required param: hyperfleetApiBaseUrl (source: env.HYPERFLEET_API_BASE_URL)
config_criteria_integration_test.go:410: Required param: hyperfleetApiVersion (source: env.HYPERFLEET_API_VERSION)
config_criteria_integration_test.go:410: Required param: hyperfleetApiToken (source: env.HYPERFLEET_API_TOKEN)
config_criteria_integration_test.go:410: Required param: clusterId (source: event.cluster_id)
config_criteria_integration_test.go:410: Required param: resourceId (source: event.resource_id)
config_criteria_integration_test.go:410: Required param: resourceType (source: event.resource_type)
config_criteria_integration_test.go:410: Required param: eventGenerationId (source: event.generation)
config_criteria_integration_test.go:410: Required param: eventHref (source: event.href)
=== RUN TestConfigParameterExtraction/verify_parameter_sources
--- PASS: TestConfigParameterExtraction (0.01s)
--- PASS: TestConfigParameterExtraction/verify_required_parameters (0.00s)
--- PASS: TestConfigParameterExtraction/verify_parameter_sources (0.00s)
PASS
ok github.com/openshift-hyperfleet/hyperfleet-adapter/test/integration/config-loader 0.173s
=== RUN TestIntegration_NewClient
client_integration_test.go:20: Using pre-built envtest integration test strategy
client_integration_test.go:20: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:56:56 github.com/testcontainers/testcontainers-go - Connected to docker:
Server Version: 29.0.2
API Version: 1.51
Operating System: Ubuntu 24.04.3 LTS (containerized)
Total Memory: 7947 MB
Testcontainers for Go Version: v0.40.0
Resolved Docker Host: unix:///var/run/docker.sock
Resolved Docker Socket Path: /var/run/docker.sock
Test SessionID: 82c8b574467739f511f56499c116b963355283d6bfa38a54635249c396993c32
Test ProcessID: b6971cbd-be36-4007-8555-57aa2b3fdc8d
2025/11/28 07:56:56 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:57:02 No image auth found for https://index.docker.io/v1/. Setting empty credentials for the image: testcontainers/ryuk:0.13.0. This is expected for public images. Details: credentials not found in native keychain
2025/11/28 07:57:05 🐳 Creating container for image testcontainers/ryuk:0.13.0
2025/11/28 07:57:07 ✅ Container created: 1f8c209a00ae
2025/11/28 07:57:07 🐳 Starting container: 1f8c209a00ae
2025/11/28 07:57:07 ✅ Container started: 1f8c209a00ae
2025/11/28 07:57:07 ⏳ Waiting for container id 1f8c209a00ae image: testcontainers/ryuk:0.13.0. Waiting for: port 8080/tcp to be listening
2025/11/28 07:57:07 Shell not found in container
2025/11/28 07:57:07 🔔 Container is ready: 1f8c209a00ae
2025/11/28 07:57:07 ✅ Container created: 69440d90008b
2025/11/28 07:57:07 🐳 Starting container: 69440d90008b
2025/11/28 07:57:08 ✅ Container started: 69440d90008b
2025/11/28 07:57:08 ⏳ Waiting for container id 69440d90008b image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:57:12 🔔 Container is ready: 69440d90008b
client_integration_test.go:20: envtest container started successfully (host: localhost)
=== RUN TestIntegration_NewClient/client_is_properly_initialized
2025/11/28 07:57:17 🐳 Stopping container: 69440d90008b
2025/11/28 07:57:27 ✅ Container stopped: 69440d90008b
2025/11/28 07:57:27 🐳 Terminating container: 69440d90008b
2025/11/28 07:57:27 🚫 Container terminated: 69440d90008b
=== NAME TestIntegration_NewClient
container.go:183: Stopping and removing envtest container (ID: 69440d90008bca696dee9b2c7677c0c767a2a6fda9c937c32f482caa443477f0)...
2025/11/28 07:57:27 🐳 Stopping container: 69440d90008b
2025/11/28 07:57:27 🐳 Terminating container: 69440d90008b
2025/11/28 07:57:27 🚫 Container terminated: 69440d90008b
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: 69440d90008bca696dee9b2c7677c0c767a2a6fda9c937c32f482caa443477f0
container.go:199: Force-removed container 69440d90008bca696dee9b2c7677c0c767a2a6fda9c937c32f482caa443477f0 using docker
--- PASS: TestIntegration_NewClient (31.21s)
--- PASS: TestIntegration_NewClient/client_is_properly_initialized (0.00s)
=== RUN TestIntegration_CreateResource
client_integration_test.go:32: Using pre-built envtest integration test strategy
client_integration_test.go:32: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:57:27 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:57:27 ✅ Container created: d6e196f8528e
2025/11/28 07:57:27 🐳 Starting container: d6e196f8528e
2025/11/28 07:57:27 ✅ Container started: d6e196f8528e
2025/11/28 07:57:27 ⏳ Waiting for container id d6e196f8528e image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:57:32 🔔 Container is ready: d6e196f8528e
client_integration_test.go:32: envtest container started successfully (host: localhost)
=== RUN TestIntegration_CreateResource/create_namespace
=== RUN TestIntegration_CreateResource/create_configmap
2025/11/28 07:57:37 🐳 Stopping container: d6e196f8528e
2025/11/28 07:57:47 ✅ Container stopped: d6e196f8528e
2025/11/28 07:57:47 🐳 Terminating container: d6e196f8528e
2025/11/28 07:57:47 🚫 Container terminated: d6e196f8528e
=== NAME TestIntegration_CreateResource
container.go:183: Stopping and removing envtest container (ID: d6e196f8528e19ac072d09672c15f06ad0df14b005c2f25439707d6ab9064e2c)...
2025/11/28 07:57:47 🐳 Stopping container: d6e196f8528e
2025/11/28 07:57:47 🐳 Terminating container: d6e196f8528e
2025/11/28 07:57:47 🚫 Container terminated: d6e196f8528e
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: d6e196f8528e19ac072d09672c15f06ad0df14b005c2f25439707d6ab9064e2c
container.go:199: Force-removed container d6e196f8528e19ac072d09672c15f06ad0df14b005c2f25439707d6ab9064e2c using docker
--- PASS: TestIntegration_CreateResource (20.31s)
--- PASS: TestIntegration_CreateResource/create_namespace (0.01s)
--- PASS: TestIntegration_CreateResource/create_configmap (0.01s)
=== RUN TestIntegration_GetResource
client_integration_test.go:98: Using pre-built envtest integration test strategy
client_integration_test.go:98: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:57:47 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:57:47 ✅ Container created: e603e087fe8a
2025/11/28 07:57:47 🐳 Starting container: e603e087fe8a
2025/11/28 07:57:48 ✅ Container started: e603e087fe8a
2025/11/28 07:57:48 ⏳ Waiting for container id e603e087fe8a image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:57:52 🔔 Container is ready: e603e087fe8a
client_integration_test.go:98: envtest container started successfully (host: localhost)
=== RUN TestIntegration_GetResource/get_existing_namespace
=== RUN TestIntegration_GetResource/get_non-existent_resource_returns_error
2025/11/28 07:57:57 🐳 Stopping container: e603e087fe8a
2025/11/28 07:58:07 ✅ Container stopped: e603e087fe8a
2025/11/28 07:58:07 🐳 Terminating container: e603e087fe8a
2025/11/28 07:58:07 🚫 Container terminated: e603e087fe8a
=== NAME TestIntegration_GetResource
container.go:183: Stopping and removing envtest container (ID: e603e087fe8a07c620da0509b0c11a86882b38fb22b7965980fb86b89a4d89aa)...
2025/11/28 07:58:07 🐳 Stopping container: e603e087fe8a
2025/11/28 07:58:07 🐳 Terminating container: e603e087fe8a
2025/11/28 07:58:07 🚫 Container terminated: e603e087fe8a
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: e603e087fe8a07c620da0509b0c11a86882b38fb22b7965980fb86b89a4d89aa
container.go:199: Force-removed container e603e087fe8a07c620da0509b0c11a86882b38fb22b7965980fb86b89a4d89aa using docker
--- PASS: TestIntegration_GetResource (19.81s)
--- PASS: TestIntegration_GetResource/get_existing_namespace (0.01s)
--- PASS: TestIntegration_GetResource/get_non-existent_resource_returns_error (0.00s)
=== RUN TestIntegration_ListResources
client_integration_test.go:136: Using pre-built envtest integration test strategy
client_integration_test.go:136: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:58:07 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:58:07 ✅ Container created: 10173ebd549e
2025/11/28 07:58:07 🐳 Starting container: 10173ebd549e
2025/11/28 07:58:08 ✅ Container started: 10173ebd549e
2025/11/28 07:58:08 ⏳ Waiting for container id 10173ebd549e image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:58:12 🔔 Container is ready: 10173ebd549e
client_integration_test.go:136: envtest container started successfully (host: localhost)
=== RUN TestIntegration_ListResources/list_configmaps_with_label_selector
2025/11/28 07:58:17 🐳 Stopping container: 10173ebd549e
2025/11/28 07:58:27 ✅ Container stopped: 10173ebd549e
2025/11/28 07:58:27 🐳 Terminating container: 10173ebd549e
2025/11/28 07:58:27 🚫 Container terminated: 10173ebd549e
=== NAME TestIntegration_ListResources
container.go:183: Stopping and removing envtest container (ID: 10173ebd549ea40dcf2064b991d54ecfaa11daaa7f3fc64b8bef6077358a35d2)...
2025/11/28 07:58:27 🐳 Stopping container: 10173ebd549e
2025/11/28 07:58:27 🐳 Terminating container: 10173ebd549e
2025/11/28 07:58:27 🚫 Container terminated: 10173ebd549e
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: 10173ebd549ea40dcf2064b991d54ecfaa11daaa7f3fc64b8bef6077358a35d2
container.go:199: Force-removed container 10173ebd549ea40dcf2064b991d54ecfaa11daaa7f3fc64b8bef6077358a35d2 using docker
--- PASS: TestIntegration_ListResources (19.94s)
--- PASS: TestIntegration_ListResources/list_configmaps_with_label_selector (0.01s)
=== RUN TestIntegration_UpdateResource
client_integration_test.go:180: Using pre-built envtest integration test strategy
client_integration_test.go:180: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:58:27 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:58:27 ✅ Container created: 1e68f773cf5c
2025/11/28 07:58:27 🐳 Starting container: 1e68f773cf5c
2025/11/28 07:58:27 ✅ Container started: 1e68f773cf5c
2025/11/28 07:58:27 ⏳ Waiting for container id 1e68f773cf5c image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:58:31 🔔 Container is ready: 1e68f773cf5c
client_integration_test.go:180: envtest container started successfully (host: localhost)
=== RUN TestIntegration_UpdateResource/update_configmap_data
2025/11/28 07:58:36 🐳 Stopping container: 1e68f773cf5c
2025/11/28 07:58:46 ✅ Container stopped: 1e68f773cf5c
2025/11/28 07:58:46 🐳 Terminating container: 1e68f773cf5c
2025/11/28 07:58:46 🚫 Container terminated: 1e68f773cf5c
=== NAME TestIntegration_UpdateResource
container.go:183: Stopping and removing envtest container (ID: 1e68f773cf5c08bf353d69097f493769a5a0fb615b3df244f7b59b2577e6b675)...
2025/11/28 07:58:46 🐳 Stopping container: 1e68f773cf5c
2025/11/28 07:58:46 🐳 Terminating container: 1e68f773cf5c
2025/11/28 07:58:46 🚫 Container terminated: 1e68f773cf5c
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: 1e68f773cf5c08bf353d69097f493769a5a0fb615b3df244f7b59b2577e6b675
container.go:199: Force-removed container 1e68f773cf5c08bf353d69097f493769a5a0fb615b3df244f7b59b2577e6b675 using docker
--- PASS: TestIntegration_UpdateResource (19.00s)
--- PASS: TestIntegration_UpdateResource/update_configmap_data (0.01s)
=== RUN TestIntegration_DeleteResource
client_integration_test.go:226: Using pre-built envtest integration test strategy
client_integration_test.go:226: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:58:46 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:58:46 ✅ Container created: 82d624fb189c
2025/11/28 07:58:46 🐳 Starting container: 82d624fb189c
2025/11/28 07:58:46 ✅ Container started: 82d624fb189c
2025/11/28 07:58:46 ⏳ Waiting for container id 82d624fb189c image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:58:51 🔔 Container is ready: 82d624fb189c
client_integration_test.go:226: envtest container started successfully (host: localhost)
=== RUN TestIntegration_DeleteResource/delete_namespace
2025/11/28 07:58:56 🐳 Stopping container: 82d624fb189c
2025/11/28 07:59:06 ✅ Container stopped: 82d624fb189c
2025/11/28 07:59:06 🐳 Terminating container: 82d624fb189c
2025/11/28 07:59:06 🚫 Container terminated: 82d624fb189c
=== NAME TestIntegration_DeleteResource
container.go:183: Stopping and removing envtest container (ID: 82d624fb189c0f14be262922765bcbd65a34f1499a6fab7878b3509dc7544148)...
2025/11/28 07:59:06 🐳 Stopping container: 82d624fb189c
2025/11/28 07:59:06 🐳 Terminating container: 82d624fb189c
2025/11/28 07:59:06 🚫 Container terminated: 82d624fb189c
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: 82d624fb189c0f14be262922765bcbd65a34f1499a6fab7878b3509dc7544148
container.go:199: Force-removed container 82d624fb189c0f14be262922765bcbd65a34f1499a6fab7878b3509dc7544148 using docker
--- PASS: TestIntegration_DeleteResource (20.33s)
--- PASS: TestIntegration_DeleteResource/delete_namespace (0.12s)
=== RUN TestIntegration_ResourceLifecycle
client_integration_test.go:271: Using pre-built envtest integration test strategy
client_integration_test.go:271: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:59:06 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:59:07 ✅ Container created: 922daf046d13
2025/11/28 07:59:07 🐳 Starting container: 922daf046d13
2025/11/28 07:59:07 ✅ Container started: 922daf046d13
2025/11/28 07:59:07 ⏳ Waiting for container id 922daf046d13 image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:59:11 🔔 Container is ready: 922daf046d13
client_integration_test.go:271: envtest container started successfully (host: localhost)
=== RUN TestIntegration_ResourceLifecycle/full_configmap_lifecycle
2025/11/28 07:59:16 🐳 Stopping container: 922daf046d13
2025/11/28 07:59:26 ✅ Container stopped: 922daf046d13
2025/11/28 07:59:26 🐳 Terminating container: 922daf046d13
2025/11/28 07:59:26 🚫 Container terminated: 922daf046d13
=== NAME TestIntegration_ResourceLifecycle
container.go:183: Stopping and removing envtest container (ID: 922daf046d1365f3d9a899c7437533208780bd32c7710d778c72d35870dbe710)...
2025/11/28 07:59:26 🐳 Stopping container: 922daf046d13
2025/11/28 07:59:26 🐳 Terminating container: 922daf046d13
2025/11/28 07:59:26 🚫 Container terminated: 922daf046d13
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: 922daf046d1365f3d9a899c7437533208780bd32c7710d778c72d35870dbe710
container.go:199: Force-removed container 922daf046d1365f3d9a899c7437533208780bd32c7710d778c72d35870dbe710 using docker
--- PASS: TestIntegration_ResourceLifecycle (19.79s)
--- PASS: TestIntegration_ResourceLifecycle/full_configmap_lifecycle (0.02s)
=== RUN TestIntegration_PatchResource
client_integration_test.go:332: Using pre-built envtest integration test strategy
client_integration_test.go:332: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:59:26 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:59:26 ✅ Container created: fde0f3eb8932
2025/11/28 07:59:26 🐳 Starting container: fde0f3eb8932
2025/11/28 07:59:27 ✅ Container started: fde0f3eb8932
2025/11/28 07:59:27 ⏳ Waiting for container id fde0f3eb8932 image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:59:30 🔔 Container is ready: fde0f3eb8932
client_integration_test.go:332: envtest container started successfully (host: localhost)
=== RUN TestIntegration_PatchResource/patch_configmap_adds_new_data_field
=== RUN TestIntegration_PatchResource/patch_configmap_updates_existing_field
=== RUN TestIntegration_PatchResource/patch_non-existent_resource_returns_error
=== RUN TestIntegration_PatchResource/patch_with_invalid_JSON_returns_error
2025/11/28 07:59:35 🐳 Stopping container: fde0f3eb8932
2025/11/28 07:59:45 ✅ Container stopped: fde0f3eb8932
2025/11/28 07:59:45 🐳 Terminating container: fde0f3eb8932
2025/11/28 07:59:45 🚫 Container terminated: fde0f3eb8932
=== NAME TestIntegration_PatchResource
container.go:183: Stopping and removing envtest container (ID: fde0f3eb8932f1532f693e5bf3a815867b23a1f91f07511d2c420b973fb26e35)...
2025/11/28 07:59:45 🐳 Stopping container: fde0f3eb8932
2025/11/28 07:59:45 🐳 Terminating container: fde0f3eb8932
2025/11/28 07:59:45 🚫 Container terminated: fde0f3eb8932
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: fde0f3eb8932f1532f693e5bf3a815867b23a1f91f07511d2c420b973fb26e35
container.go:199: Force-removed container fde0f3eb8932f1532f693e5bf3a815867b23a1f91f07511d2c420b973fb26e35 using docker
--- PASS: TestIntegration_PatchResource (19.15s)
--- PASS: TestIntegration_PatchResource/patch_configmap_adds_new_data_field (0.01s)
--- PASS: TestIntegration_PatchResource/patch_configmap_updates_existing_field (0.01s)
--- PASS: TestIntegration_PatchResource/patch_non-existent_resource_returns_error (0.00s)
--- PASS: TestIntegration_PatchResource/patch_with_invalid_JSON_returns_error (0.00s)
=== RUN TestIntegration_ErrorScenarios
client_integration_test.go:464: Using pre-built envtest integration test strategy
client_integration_test.go:464: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 07:59:45 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 07:59:45 ✅ Container created: 28fe878ef720
2025/11/28 07:59:45 🐳 Starting container: 28fe878ef720
2025/11/28 07:59:46 ✅ Container started: 28fe878ef720
2025/11/28 07:59:46 ⏳ Waiting for container id 28fe878ef720 image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 07:59:50 🔔 Container is ready: 28fe878ef720
client_integration_test.go:464: envtest container started successfully (host: localhost)
=== RUN TestIntegration_ErrorScenarios/create_duplicate_resource_returns_AlreadyExists_error
=== RUN TestIntegration_ErrorScenarios/list_with_invalid_label_selector_returns_error
=== RUN TestIntegration_ErrorScenarios/get_with_empty_name_returns_error
=== RUN TestIntegration_ErrorScenarios/delete_already_deleted_resource_succeeds
=== RUN TestIntegration_ErrorScenarios/update_with_missing_resourceVersion_still_works
2025/11/28 07:59:55 🐳 Stopping container: 28fe878ef720
2025/11/28 08:00:05 ✅ Container stopped: 28fe878ef720
2025/11/28 08:00:05 🐳 Terminating container: 28fe878ef720
2025/11/28 08:00:05 🚫 Container terminated: 28fe878ef720
=== NAME TestIntegration_ErrorScenarios
container.go:183: Stopping and removing envtest container (ID: 28fe878ef720af06f7acf046c56f19c074946ab5caca2c41983c642ea29964d6)...
2025/11/28 08:00:05 🐳 Stopping container: 28fe878ef720
2025/11/28 08:00:05 🐳 Terminating container: 28fe878ef720
2025/11/28 08:00:05 🚫 Container terminated: 28fe878ef720
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: 28fe878ef720af06f7acf046c56f19c074946ab5caca2c41983c642ea29964d6
container.go:199: Force-removed container 28fe878ef720af06f7acf046c56f19c074946ab5caca2c41983c642ea29964d6 using docker
--- PASS: TestIntegration_ErrorScenarios (19.57s)
--- PASS: TestIntegration_ErrorScenarios/create_duplicate_resource_returns_AlreadyExists_error (0.01s)
--- PASS: TestIntegration_ErrorScenarios/list_with_invalid_label_selector_returns_error (0.00s)
--- PASS: TestIntegration_ErrorScenarios/get_with_empty_name_returns_error (0.00s)
--- PASS: TestIntegration_ErrorScenarios/delete_already_deleted_resource_succeeds (0.01s)
--- PASS: TestIntegration_ErrorScenarios/update_with_missing_resourceVersion_still_works (0.01s)
=== RUN TestIntegration_DifferentResourceTypes
client_integration_test.go:582: Using pre-built envtest integration test strategy
client_integration_test.go:582: Starting envtest container (image: localhost/hyperfleet-integration-test:latest)...
2025/11/28 08:00:05 🐳 Creating container for image localhost/hyperfleet-integration-test:latest
2025/11/28 08:00:05 ✅ Container created: 8116c7e19fa4
2025/11/28 08:00:05 🐳 Starting container: 8116c7e19fa4
2025/11/28 08:00:05 ✅ Container started: 8116c7e19fa4
2025/11/28 08:00:05 ⏳ Waiting for container id 8116c7e19fa4 image: localhost/hyperfleet-integration-test:latest. Waiting for: all of: [port 6443/tcp to be listening, log message "Envtest is running"]
2025/11/28 08:00:09 🔔 Container is ready: 8116c7e19fa4
client_integration_test.go:582: envtest container started successfully (host: localhost)
=== RUN TestIntegration_DifferentResourceTypes/create_and_get_service
=== RUN TestIntegration_DifferentResourceTypes/create_and_list_pods
=== RUN TestIntegration_DifferentResourceTypes/create_secret_with_data
2025/11/28 08:00:15 🐳 Stopping container: 8116c7e19fa4
2025/11/28 08:00:26 ✅ Container stopped: 8116c7e19fa4
2025/11/28 08:00:26 🐳 Terminating container: 8116c7e19fa4
2025/11/28 08:00:26 🚫 Container terminated: 8116c7e19fa4
=== NAME TestIntegration_DifferentResourceTypes
container.go:183: Stopping and removing envtest container (ID: 8116c7e19fa4a2d9aaa9c113af1ff315aca0adfdae619e777bdbc9556a0727a0)...
2025/11/28 08:00:26 🐳 Stopping container: 8116c7e19fa4
2025/11/28 08:00:26 🐳 Terminating container: 8116c7e19fa4
2025/11/28 08:00:26 🚫 Container terminated: 8116c7e19fa4
container.go:195: Warning: Failed to terminate envtest container gracefully: Error response from daemon: No such container: 8116c7e19fa4a2d9aaa9c113af1ff315aca0adfdae619e777bdbc9556a0727a0
container.go:199: Force-removed container 8116c7e19fa4a2d9aaa9c113af1ff315aca0adfdae619e777bdbc9556a0727a0 using docker
--- PASS: TestIntegration_DifferentResourceTypes (20.78s)
--- PASS: TestIntegration_DifferentResourceTypes/create_and_get_service (0.01s)
--- PASS: TestIntegration_DifferentResourceTypes/create_and_list_pods (1.04s)
--- PASS: TestIntegration_DifferentResourceTypes/create_secret_with_data (0.00s)
PASS
ok github.com/openshift-hyperfleet/hyperfleet-adapter/test/integration/k8s_client 209.938s
? github.com/openshift-hyperfleet/hyperfleet-adapter/test/integration/testutil [no test files]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Integration tests passed!

</details>

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

♻️ Duplicate comments (2)
test/integration/testutil/container.go (1)

236-261: Force cleanup implementation addresses the past review feedback.

The simplified error logging at lines 254-255 already incorporates the suggestion from the previous review to log all errors rather than filtering by string matching.

internal/criteria/cel_evaluator.go (1)

252-256: String escaping is incomplete for CEL string literals.

This issue was previously identified: the formatCELValue function only escapes double quotes in strings (line 255), missing backslashes, newlines, tabs, and other control characters. This creates inconsistency with line 300, which correctly uses strconv.Quote.

Note: This was flagged in a previous review and remains unaddressed.

🧹 Nitpick comments (15)
test/integration/testutil/container.go (1)

156-169: Prefer deferring context cancel for safety.

The cancel() call at line 168 should be deferred immediately after context creation to prevent a potential resource leak if Terminate or subsequent code panics.

 		if container != nil {
 			t.Logf("Attempt %d failed but container was created. Terminating...", attempt)
 			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+			defer cancel()
 			if termErr := container.Terminate(ctx); termErr != nil {
 				t.Logf("Warning: Failed to terminate failed container from attempt %d: %v", attempt, termErr)
 				// Try force cleanup
 				if cid := container.GetContainerID(); cid != "" {
 					forceCleanupContainer(t, cid)
 				}
 			}
-			cancel()
 		}
test/integration/k8s_client/helper_testcontainers_k3s.go (1)

96-106: Good defensive cleanup registration, but inner nil check is redundant.

The t.Cleanup registration immediately after container creation prevents leaks if subsequent assertions fail. However, the inner nil check at line 100 is redundant since the outer check at line 98 already guarantees k3sContainer is non-nil when the cleanup function is registered.

 	// Register cleanup immediately after creation to prevent leaks if assertions fail
 	if k3sContainer != nil {
 		t.Cleanup(func() {
-			if k3sContainer != nil {
-				ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-				defer cancel()
-				_ = k3sContainer.Terminate(ctx)
-			}
+			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+			defer cancel()
+			_ = k3sContainer.Terminate(ctx)
 		})
 	}
internal/config_loader/types.go (2)

136-141: Potential type inconsistency with criteria.ConditionDef.

The Condition type here uses string for Operator, while internal/criteria/evaluator.go defines ConditionDef with Operator string as well, but internal/criteria/types.go defines a typed Operator alias. Consider aligning these types or adding a note about the conversion path.

Based on the relevant code snippets, internal/criteria/types.go defines Operator string as a type alias. The conversion happens in EvaluateConditionsWithResult where Operator(cond.Operator) is used. This works but could be more explicit.


46-61: Consider documenting the mutual exclusivity of Parameter configuration options.

The Parameter struct has multiple optional fields (Source, Build, BuildRef, FetchExternalResource) that appear mutually exclusive. Adding a doc comment clarifying which combinations are valid would help users configure correctly.

internal/criteria/evaluator.go (3)

85-100: Redundant return after IsFieldNotFound check.

Lines 94-97 have a redundant pattern - after checking IsFieldNotFound(err) and returning false, the code falls through to another return false on line 97.

 func (e *Evaluator) EvaluateConditionSafe(field string, operator Operator, value interface{}) bool {
 	result, err := e.EvaluateConditionWithResult(field, operator, value)
 	if err != nil {
 		// For "exists" operator, field not found means not exists
 		if operator == OperatorExists {
 			return false
 		}
-		// For other operators, check if it's a field not found error
-		if IsFieldNotFound(err) {
-			return false
-		}
+		// For any error (including field not found), return false
 		return false
 	}
 	return result.Matched
 }

215-240: CEL evaluator created on every call - consider caching.

Each EvaluateCEL* method creates a new CELEvaluator instance. For repeated evaluations with the same context, this could be inefficient. Consider caching the CEL evaluator in the Evaluator struct or documenting this as a known trade-off.


270-275: ConditionDef defined late in file - consider moving to types.go.

ConditionDef is a public type that should arguably live in types.go alongside other type definitions. This would improve discoverability and align with the package structure.

internal/config_loader/loader_e2e_test.go (2)

12-18: Consider more robust project root detection.

The getProjectRoot function assumes a fixed directory structure. If the file moves, this breaks. Consider using go.mod detection or environment variable fallback for more resilience.

// Alternative: detect project root by finding go.mod
func getProjectRoot() string {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		panic("failed to get caller info")
	}
	dir := filepath.Dir(filename)
	for {
		if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
			return dir
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			// Fallback to original logic
			return filepath.Join(filepath.Dir(filename), "../..")
		}
		dir = parent
	}
}

13-14: Ignoring error from runtime.Caller.

The ok return value from runtime.Caller(0) is discarded. While unlikely to fail, this could cause cryptic errors if it does.

 func getProjectRoot() string {
-	_, filename, _, _ := runtime.Caller(0)
+	_, filename, _, ok := runtime.Caller(0)
+	if !ok {
+		panic("failed to get caller information for project root detection")
+	}
 	// filename is .../internal/config_loader/loader_e2e_test.go
 	// We need to go up 2 levels to get to the project root
 	return filepath.Join(filepath.Dir(filename), "../..")
 }
cmd/adapter/main.go (2)

91-92: Remove extra blank line.

There's a redundant blank line between the API client creation error handling and the signal handling section.

 	}
-
 
 	// Handle signals for graceful shutdown

145-159: TODO placeholder with unused variable workaround - track as technical debt.

The _ = adapterConfig and _ = apiClient lines suppress compiler warnings but indicate incomplete implementation. Consider adding a GitHub issue reference to track this work.

Would you like me to open a GitHub issue to track the implementation of the event processing logic using adapterConfig and apiClient?

internal/config_loader/validator.go (4)

18-50: Validation error aggregation is solid; consider adjusting empty-error behavior

The aggregation pattern looks good and produces readable, path-scoped messages. One minor nit: ValidationErrors.Error() returning "no validation errors" when len(ve.Errors) == 0 can be surprising if the type ever escapes with an empty slice (it still “looks like” an error string). Consider either:

  • Returning an empty string in that case, or
  • Ensuring *ValidationErrors is never used as an error when empty (documenting that invariant).

This is non-blocking but worth aligning before broader use.


56-96: Make Validator either single-use by contract or reset its state on each Validate call

Validator.Validate() never clears v.errors, so if a single Validator instance is reused, errors from a previous run will be retained and cause HasErrors() to stay true even if the subsequent config is clean. That’s easy to hit in tests or long-lived components that cache a Validator.

Two options:

  1. Treat Validator as single-use and document that clearly, or
  2. Reset state at the start of Validate():
 func (v *Validator) Validate() error {
   if v.config == nil {
     return fmt.Errorf("config is nil")
   }
+
+  // Reset error aggregation so the validator can be safely reused.
+  v.errors = &ValidationErrors{}
 
   // Initialize validation context
   v.collectDefinedParameters()

(You already recompute definedParams and celEnv, so resetting errors is the main missing piece.)


281-317: initCELEnv correctly derives variables; watch interaction with resources if it ever appears in definedParams

Building the CEL environment from definedParams with DynType is a pragmatic choice for validation, and the addedRoots map prevents redundant variable declarations.

One subtle edge case: if GetDefinedVariables() ever returns keys like resources.foo, the loop will register a resources variable with DynType, mark that root as added, and then the later special-case:

if !addedRoots["resources"] {
    options = append(options, cel.Variable("resources", cel.MapType(cel.StringType, cel.DynType)))
}

won’t run, so resources won’t have the intended map<string, dyn> type. If resources.* is meant to be purely reserved (never in definedParams), this is fine; if not, you may want to either (a) exclude resources.* from definedParams, or (b) special-case it in the loop to always use the map type.

Worth confirming expectations before this spreads into more configs.


398-457: Kubernetes manifest validation is a good safety net; consider tightening non-map and ref/refs handling

The manifest validation provides a useful semantic layer on top of schema validation, especially enforcing apiVersion, kind, and metadata.name.

Two potential refinements:

  1. Non-map manifests
    Right now, if resource.Manifest isn’t a map[string]interface{}, it’s silently skipped:

    manifest, ok := resource.Manifest.(map[string]interface{})
    if !ok {
        continue
    }

    If non-map manifests are invalid by design (most likely), you might prefer to add an explicit error rather than silently ignoring them.

  2. ref / refs type assumptions

    • ref is asserted as string and non-empty, but a non-string value will just bypass this branch instead of producing a clear error.
    • refs is only checked for presence, not shape.

    If the schema validator doesn’t already guarantee types here, you might want to validate ref is a non-empty string and refs has the expected type, to give better feedback on malformed configurations.

These are polish-level tweaks; the current checks are already helpful.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc4a921 and c8c7587.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_e2e_test.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • configs/README.md
  • configs/templates/cluster-status-payload.yaml
  • internal/config_loader/README.md
  • internal/criteria/README.md
  • configs/templates/job.yaml
  • internal/config_loader/loader_test.go
  • internal/criteria/evaluator_scenarios_test.go
  • internal/hyperfleet_api/README.md
  • internal/hyperfleet_api/client.go
🧰 Additional context used
🧬 Code graph analysis (14)
test/integration/k8s_client/helper_testcontainers_k3s.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (65-69)
internal/config_loader/validator_test.go (2)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
test/integration/config-loader/config_criteria_integration_test.go (4)
internal/config_loader/loader.go (1)
  • Load (85-108)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorExists (26-26)
internal/criteria/evaluator.go (2)
  • NewEvaluator (41-48)
  • ConditionDef (271-275)
internal/criteria/cel_evaluator.go (2)
  • ConditionsToCEL (316-331)
  • ConditionToCEL (202-244)
internal/config_loader/loader_e2e_test.go (2)
internal/config_loader/loader.go (1)
  • Load (85-108)
internal/config_loader/types.go (3)
  • Metadata (12-16)
  • APICall (114-122)
  • ExtractField (131-134)
internal/criteria/evaluator_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
  • EvaluationError (97-101)
internal/criteria/evaluator.go (3)
  • NewEvaluator (41-48)
  • IsFieldNotFound (527-530)
  • FieldNotFoundError (516-520)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (114-122)
  • Resource (144-151)
  • SelectorConfig (161-164)
  • Parameter (47-61)
  • FetchExternalResource (96-101)
  • Precondition (104-111)
internal/config_loader/loader.go (3)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
internal/config_loader/validator.go (3)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (114-122)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-61)
  • Resource (144-151)
  • Precondition (104-111)
  • PostAction (173-177)
  • HyperfleetAPIConfig (35-39)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (34-51)
  • ConditionToCEL (202-244)
  • ConditionsToCEL (316-331)
internal/criteria/evaluator.go (2)
  • ConditionDef (271-275)
  • NewEvaluator (41-48)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (450-462)
internal/config_loader/validator.go (1)
  • Validate (466-468)
test/integration/k8s_client/helper_envtest_prebuilt.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (65-69)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • OperatorExists (26-26)
  • EvaluationError (97-101)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
internal/criteria/cel_evaluator.go (4)
  • CELResult (22-31)
  • NewCELEvaluator (34-51)
  • ConditionToCEL (202-244)
  • ConditionsToCEL (316-331)
🔇 Additional comments (41)
test/integration/k8s_client/helper_envtest_prebuilt.go (2)

42-51: Good addition of explicit container termination in Cleanup.

The implementation correctly uses a timeout context and logs errors rather than failing, which is appropriate for cleanup code. The comment about idempotency addresses potential double-termination concerns.


135-147: Well-structured setup failure cleanup pattern.

The deferred cleanup with setupSuccess flag ensures containers are terminated immediately if setup fails, preventing resource leaks during test failures. This complements the t.Cleanup already registered by StartContainer.

test/integration/testutil/container.go (2)

177-204: Good enhancement: capturing container ID for reliable cleanup.

Capturing the container ID and name before registering cleanup ensures the correct container is targeted even if the variable is reassigned. The fallback to force cleanup is a solid resilience pattern.


263-298: Useful utility for cleaning up leaked containers from previous test runs.

The function correctly iterates through runtimes with fallback and handles the multi-line container ID output properly. Consider documenting that imagePattern must be a trusted value to avoid command injection in non-test contexts.

OWNERS (1)

2-2: LGTM: Quoting numeric approver ID.

Quoting the numeric approver ID prevents potential YAML parsing ambiguities where the parser might interpret it as an integer rather than a string.

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

123-128: LGTM: imageTag parameter addition.

The new imageTag parameter provides flexibility for container image versioning with a sensible default. The optional nature (required: false) allows deployments to use the default while enabling customization when needed.

configs/broker-configmap-pubsub-template.yaml (1)

24-28: LGTM: Consistent renaming for clarity.

The addition of the BROKER_ prefix to SUBSCRIPTION_ID and TOPIC improves clarity and reduces ambiguity. The renaming is applied consistently across the template and example deployment.

pkg/errors/api_error.go (1)

14-154: LGTM: Well-designed API error type.

The APIError type provides comprehensive HTTP error context with proper error wrapping support. The implementation includes:

  • Rich error details (method, URL, status, body, timing, attempts)
  • Proper Unwrap() for errors.Is/As compatibility
  • Convenient status code classification helpers
  • Safe response body accessors

The design follows Go error handling best practices and provides excellent observability for HTTP failures.

internal/criteria/types.go (1)

7-112: LGTM: Clean criteria evaluation type system.

The types provide a solid foundation for the criteria evaluation subsystem:

  • Operator system: Eight well-defined operators with validation and utility functions
  • EvaluationContext: Clean data storage with nested field access and context merging
  • EvaluationError: Proper error type with field-specific context and unwrapping support

All types follow Go best practices and provide clear, intuitive APIs.

internal/criteria/cel_evaluator.go (1)

34-174: LGTM: Robust CEL evaluation implementation.

The CEL evaluator provides comprehensive expression evaluation with:

  • Dynamic type inference from evaluation context
  • Graceful handling of type-check failures (appropriate for dynamic data)
  • Both boolean and generic evaluation modes
  • Proper error propagation throughout the evaluation pipeline

The implementation correctly handles edge cases like empty expressions and non-boolean results.

test/testdata/adapter_config_valid.yaml (1)

1-202: LGTM: Comprehensive test configuration.

This test data effectively demonstrates the complete adapter configuration structure, including:

  • API client configuration with retry settings
  • Parameter extraction from environment and events
  • Preconditions with both structured conditions and CEL expressions
  • Resource manifests with template interpolation
  • Post-processing with complex CEL-based status building
  • Conditional post-actions

The configuration exercises all major features introduced in this PR and serves as good documentation of expected usage patterns.

internal/config_loader/types.go (2)

1-9: LGTM! Well-structured root configuration type.

The AdapterConfig type follows Kubernetes-style API conventions with apiVersion, kind, metadata, and spec fields. Good use of YAML tags.


186-189: Unable to verify ManifestRef usage due to repository access constraints.

The repository cannot be cloned in the sandbox environment, preventing automated verification of whether ManifestRef is used elsewhere in the codebase. Manual verification is required by inspecting related files in internal/config_loader/ and searching for imports or usages of this type across the project.

internal/criteria/evaluator.go (3)

1-48: LGTM! Clean evaluator initialization.

The Evaluator type and NewEvaluator constructor follow good patterns with nil-safety for the context parameter.


388-418: LGTM! Comprehensive numeric type handling.

The toFloat64 function handles all Go numeric types correctly. This is important for cross-type comparisons.


420-423: I'm unable to clone the repository to verify the usage of getNestedField. However, based on the information provided in your review comment, I can still analyze the situation:

The review comment states that:

  1. getNestedField is defined at lines 420-423 in internal/criteria/evaluator.go
  2. The code uses e.context.GetNestedField(field) instead
  3. The reviewer questions if this is dead code

Since I cannot access the repository to search for actual usages, I need to request your input to help verify this concern. Could you provide:

  1. The results of searching for all occurrences of getNestedField in the codebase (case-sensitive)?
  2. Is getNestedField exported (capitalized) or unexported (lowercase)?
  3. Are there any references to this function in tests, documentation, or other files?

Alternatively, if you have access to run the verification yourself, the original shell script in your review comment should help identify all usages.

test/testdata/templates/deployment.yaml (1)

1-27: LGTM! Appropriate test template.

The test deployment template correctly demonstrates templating patterns with {{ .clusterId }} and {{ .metadata.name }} placeholders. Using nginx:latest is acceptable for test data.

configs/templates/deployment.yaml (2)

1-37: LGTM! Well-structured production deployment template.

The template includes proper resource requests/limits, environment variable injection, and follows Kubernetes best practices. The template variables (clusterId, imageTag, resourceId) align with the adapter configuration parameters.


24-24: I'll help you verify whether the imageTag parameter has a default value. Let me search the configuration files for this parameter.
<function_calls>

#!/bin/bash

Search for imageTag parameter definition in YAML files

rg -n "imageTag" --type=yaml -A5 -B2


</function_calls>

Please provide the output so I can analyze whether the imageTag parameter has a sensible default value defined.

internal/config_loader/loader_e2e_test.go (2)

20-101: LGTM! Comprehensive E2E test coverage.

TestLoadTemplateConfig thoroughly validates the loaded configuration structure including metadata, adapter info, API config, params, preconditions, resources, and post-processing. Good use of assertions and helper methods.


115-123: LGTM! Clean helper function.

The findExtractByAs helper correctly iterates by index to return a pointer to the found element rather than a copy.

cmd/adapter/main.go (2)

62-72: LGTM! Proper configuration loading with clear logging.

The config loading integrates well with the existing startup flow. Good error handling and informative log messages.


188-218: LGTM! Robust API client factory function.

The createAPIClient function properly parses duration strings, validates retry attempts, and validates backoff strategy with clear error messages for invalid values.

internal/hyperfleet_api/client_test.go (5)

1-93: LGTM! Excellent test coverage for client construction.

The tests cover various client options including timeouts, retry attempts, backoff strategies, and custom configurations. Good use of table-driven tests.


194-232: LGTM! Proper retry behavior verification.

The test correctly verifies that the client retries on 503 errors and eventually succeeds. Good use of atomic.Int32 for thread-safe counting.


378-418: Backoff calculation tests have appropriate jitter tolerance.

The ±20% tolerance range (80-120ms for 100ms base) properly accounts for the jitter that should be added to backoff delays. This is good practice for avoiding test flakiness.


486-526: LGTM! Comprehensive APIError testing.

Tests validate error message content, helper methods (IsServerError, IsClientError, IsNotFound), and response body extraction. Good coverage of the error API surface.


596-640: LGTM! Integration test for retry exhaustion with APIError.

This test validates the complete flow of retry exhaustion and proper APIError construction with all relevant details (method, status, attempts, response body). Excellent end-to-end verification.

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

1-428: LGTM! Comprehensive integration test suite.

This integration test file provides excellent end-to-end coverage of the config loading and criteria evaluation flow. The tests exercise:

  • Config template loading and validation
  • Precondition evaluation with multiple conditions
  • CEL expression conversion and evaluation
  • Failure scenarios (wrong phase/provider, missing fields)
  • Resource discovery configuration validation
  • Post-processing with nested K8s resource data
  • Null-safety handling for missing/null fields
  • Parameter extraction and source validation

The fix for the previous parameter source validation issue (lines 421-422) correctly uses strings.HasPrefix to avoid potential slice bounds panics.

internal/criteria/evaluator_test.go (1)

1-992: LGTM! Thorough evaluation engine test coverage.

This test suite provides comprehensive coverage of the criteria evaluation engine:

  • EvaluationContext operations (Set, Get, Merge, nested field access)
  • All evaluation operators (equals, notEquals, in, contains, greaterThan, lessThan, exists)
  • Evaluator methods with results and extracted fields
  • Field extraction and default value handling
  • Null-safety with missing resources and deep null paths
  • Error handling with FieldNotFoundError validation

The tests are well-structured with clear naming, proper use of table-driven patterns, and good edge case coverage.

internal/config_loader/validator_test.go (1)

1-679: LGTM! Comprehensive validator test suite.

This test file provides excellent coverage of the config validation layer:

  • Condition operator validation (valid, invalid, and missing operators)
  • Template variable validation (defined, undefined, and nested variables)
  • CEL expression validation (syntax errors and has() function usage)
  • K8s manifest validation (required fields: apiVersion, kind, metadata.name)
  • ValidationErrors formatting and aggregation
  • Built-in variable recognition

The table-driven tests are well-organized and cover both positive and negative scenarios with clear error message assertions.

internal/config_loader/validator_schema.go (1)

1-502: LGTM! Well-structured schema validator implementation.

This file introduces a robust schema validation layer with:

  • Structural validation: API version/kind, metadata, adapter spec, params, preconditions, resources, and post actions
  • Resource discovery validation: Ensures discovery configs have required fields (namespace, byName/bySelectors)
  • File reference validation: Validates existence of buildRef and manifest.ref files before loading
  • File loading: Loads YAML content from references with proper error handling
  • Path resolution: Handles both absolute and relative paths against base directory
  • Backward compatibility: Legacy wrapper functions for smooth migration

The validator follows a clear fail-fast pattern, provides detailed error messages with context paths, and properly separates validation concerns. The implementation is defensive with nil checks and proper error wrapping.

internal/config_loader/accessors.go (1)

1-239: LGTM! Clean and well-guarded accessor implementations.

This file provides a comprehensive set of accessors for the config loader:

  • Variable management: BuiltinVariables and GetDefinedVariables aggregate all available template/CEL variables from multiple sources
  • Config accessors: GetParamByName, GetResourceByName, GetPreconditionByName, GetPostActionByName with name-based lookups
  • Collection accessors: GetRequiredParams, ParamNames, ResourceNames for bulk access
  • HyperfleetAPIConfig: ParseTimeout with proper default handling
  • Resource helpers: HasManifestRef, GetManifestRefs (handles both single and array refs), UnmarshalManifest with type safety

All accessors include proper nil guards, safe type handling, and clear return semantics. The implementation is defensive and prevents panics in various nil scenarios.

internal/criteria/cel_evaluator_test.go (1)

1-367: LGTM! Comprehensive CEL evaluator test coverage.

This test suite thoroughly exercises the CEL evaluation subsystem:

  • Evaluator creation and basic evaluation: String equality, numeric comparisons, boolean variables, compound expressions (&&, ||), list membership
  • Nested data access: Testing cluster.status.phase and cluster.spec.replicas patterns
  • Type-specific evaluation: EvaluateBool and EvaluateString helpers
  • Condition-to-CEL conversion: All operators (equals, notEquals, greaterThan, lessThan, in, notIn, contains, exists) with proper CEL expression generation
  • Integration testing: EvaluateCEL, EvaluateCELBool, EvaluateConditionAsCEL, EvaluateConditionsAsCEL
  • Value formatting: formatCELValue with various types including nil, strings, numbers, booleans, slices, and error cases for unsupported types

The tests are well-organized with clear naming and good edge case coverage.

internal/config_loader/loader.go (1)

1-195: LGTM! Well-architected config loader implementation.

This file provides a robust, modular configuration loader:

  • Functional options pattern: WithAdapterVersion, WithSkipSemanticValidation, WithBaseDir for flexible configuration
  • Public API: Load/Parse with automatic baseDir resolution from file paths, environment variable support
  • Validation pipeline: Multi-stage validation with core structural checks, adapter version validation, file reference validation/loading, and optional semantic validation
  • Backward compatibility: Deprecated LoadWithVersion and ParseWithVersion wrappers maintained
  • Error handling: Proper error wrapping with context at each validation stage
  • Performance optimization: Optional semantic validation skip for performance-sensitive scenarios

The loader coordinates well with the validator and schema validator components, providing a clean separation of concerns between structural validation, file loading, and semantic validation.

internal/hyperfleet_api/types.go (1)

1-233: LGTM! Clean and well-designed HTTP client type definitions.

This file establishes a robust HTTP client model for HyperFleet API interactions:

  • Backoff strategies: Exponential, linear, and constant with sensible defaults (10s timeout, 3 retries, exponential backoff)
  • ClientConfig: Comprehensive configuration with timeout, retry, backoff, delay bounds, and default headers
  • Request model: Support for all HTTP methods with per-request overrides for timeout, retry, and backoff
  • Functional options: WithHeaders, WithHeader, WithBody, WithJSONBody, WithRequestTimeout, WithRequestRetryAttempts, WithRequestRetryBackoff for flexible request customization
  • Response helpers: IsSuccess, IsClientError, IsServerError, IsRetryable (with smart retryable status detection), BodyString
  • Client interface: Clean abstraction with Do (low-level) and verb methods (Get, Post, Put, Patch, Delete)

The design allows both client-level defaults and per-request overrides, providing flexibility while maintaining good defaults. The use of pointers for per-request overrides properly distinguishes "not set" from "set to zero/false".

internal/config_loader/validator.go (5)

111-144: Operator validation is clear and correctly centralized on the criteria registry

The operator validation logic for both preconditions and post-actions is straightforward and correctly delegates to criteria.IsValidOperator / OperatorStrings(), giving nice, discoverable error messages.

No issues from a correctness standpoint.


150-275: Template variable validation is thorough; confirm handling of reserved/template-only variables

The recursive template validation over preconditions, resources, discovery, and post sections is well covered and isVariableDefined’s “root variable” handling is a nice touch for nested paths.

One thing to double-check: if there are any reserved/template-only variables (e.g., equivalents of the CEL‑side resources.* or other engine-injected symbols) that are valid in templates but not surfaced via GetDefinedVariables(), they’ll currently be treated as undefined and produce validation errors.

If such variables exist, consider:

  • Whitelisting them in isVariableDefined, or
  • Ensuring GetDefinedVariables() includes those roots.

Otherwise this looks good.


319-368: CEL expression coverage is good; verify param.Build.Conditions value types can’t be nil

The CEL validation walks all the main expression sites (preconditions, post-action when, and post.params[*].build.{conditions,data}), which is great. One thing to explicitly verify is the shape of param.Build.Conditions values:

for condName, cond := range param.Build.Conditions {
    basePath := fmt.Sprintf("spec.post.params[%d].build.conditions.%s", i, condName)
    if cond.Status.Expression != "" {
        ...
    }
    if cond.Reason.Expression != "" {
        ...
    }
    if cond.Message.Expression != "" {
        ...
    }
}

If Status, Reason, or Message are pointer fields in the underlying type, this will panic on nil dereference. If they’re value structs, this is safe.

Please confirm the types in internal/config_loader/types.go; if any of these are pointers, add nil guards before accessing .Expression.


370-392: CEL parse vs. type-check handling is sensible

Parsing errors correctly fail validation with a path-scoped message, while type-check issues only log at V(2) and allow validation to continue. That’s a reasonable tradeoff given the use of DynType and dynamic variables.

No changes needed here.


463-468: Public Validate wrapper keeps the API simple and backward compatible

The top-level Validate(*AdapterConfig) delegating to NewValidator(config).Validate() is a clean public entry point and makes it straightforward for callers to opt into semantic validation without dealing with the struct.

Looks good.

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

♻️ Duplicate comments (2)
internal/criteria/cel_evaluator.go (2)

230-237: Re-verify null-safety for nested field existence checks.

The exists operator generates has(field) for nested paths (line 234). While OptionalTypes() is enabled (line 59), the prior review flagged that has(a.b.c) can error if intermediate selectors like a.b are null.

Verify that the CEL environment's OptionalTypes() configuration adequately handles nested has() calls without requiring explicit parent checks or optional-safe syntax.

#!/bin/bash
# Search for test cases that exercise nested exists operator with null parents
rg -n -C5 "exists.*\\..*\\." --type=go internal/criteria/ | head -100

249-253: Consider using strconv.Quote for consistent string escaping.

The string case only escapes double quotes, while the reflect.String case at line 297 uses strconv.Quote for complete escaping. For consistency and correctness, use strconv.Quote here as well.

Apply this diff:

 	case string:
-		// Escape quotes in string
-		escaped := strings.ReplaceAll(v, `"`, `\"`)
-		return fmt.Sprintf(`"%s"`, escaped), nil
+		// Use strconv.Quote for proper escaping (handles quotes, backslashes, newlines, etc.)
+		return strconv.Quote(v), nil
🧹 Nitpick comments (8)
test/integration/testutil/container.go (1)

263-298: Early return may miss containers from other runtimes.

CleanupLeakedContainers returns at line 296 after the first runtime successfully lists containers. If a developer uses both Docker and Podman in different test runs, leaked containers from one runtime won't be cleaned when using the other.

Consider continuing to check both runtimes:

 		for _, id := range containerIDs {
 			if id == "" {
 				continue
 			}
 			rmCmd := exec.Command(runtime, "rm", "-f", id)
 			if rmErr := rmCmd.Run(); rmErr != nil {
 				t.Logf("Warning: Failed to remove container %s: %v", id, rmErr)
 			} else {
 				t.Logf("Cleaned up leaked container: %s", id)
 			}
 		}
-		return // Success with this runtime
 	}
 }
test/integration/k8s_client/helper_testcontainers_k3s.go (1)

96-107: Redundant inner nil check.

The nil check at line 100 is redundant since the outer check at line 98 already confirms k3sContainer is non-nil, and the variable is never reassigned. The cleanup registration itself is a good addition for preventing leaks on assertion failures.

 	if k3sContainer != nil {
 		t.Cleanup(func() {
-			if k3sContainer != nil {
-				ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-				defer cancel()
-				_ = k3sContainer.Terminate(ctx)
-			}
+			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+			defer cancel()
+			_ = k3sContainer.Terminate(ctx)
 		})
 	}
configs/templates/job.yaml (1)

18-18: Consider using the imageTag parameter instead of hardcoding.

The image tag is hardcoded to v1.0.0, but the adapter config defines an imageTag parameter (with default v1.0.0) for this purpose. Using the parameter would allow consistent image version management.

-          image: "quay.io/hyperfleet/validator:v1.0.0"
+          image: "quay.io/hyperfleet/validator:{{ .imageTag }}"
internal/config_loader/accessors.go (1)

210-217: Variable shadowing: loop variable r shadows the receiver.

The loop variable r shadows the method receiver r *Resource, which can cause confusion and potential bugs if the receiver is accidentally referenced inside the loop.

 	// Array of refs
 	if refs, ok := manifest["refs"].([]interface{}); ok {
 		result := make([]string, 0, len(refs))
-		for _, r := range refs {
-			if s, ok := r.(string); ok && s != "" {
+		for _, ref := range refs {
+			if s, ok := ref.(string); ok && s != "" {
 				result = append(result, s)
 			}
 		}
 		return result
 	}
internal/config_loader/types.go (4)

35-39: Add validation for RetryBackoff values.

The RetryBackoff field is a string without constraints. Consider adding validation (either via struct tags or in the validator) to ensure only supported values like "linear", "exponential", "constant" are accepted.

// HyperfleetAPIConfig contains HyperFleet API configuration
type HyperfleetAPIConfig struct {
	Timeout        string `yaml:"timeout"`
	RetryAttempts  int    `yaml:"retryAttempts"`
	RetryBackoff   string `yaml:"retryBackoff"` // Supported: "linear", "exponential", "constant"
}

Alternatively, define a custom type with validation:

type RetryBackoffStrategy string

const (
	RetryBackoffLinear      RetryBackoffStrategy = "linear"
	RetryBackoffExponential RetryBackoffStrategy = "exponential"
	RetryBackoffConstant    RetryBackoffStrategy = "constant"
)

// HyperfleetAPIConfig contains HyperFleet API configuration
type HyperfleetAPIConfig struct {
	Timeout        string               `yaml:"timeout"`
	RetryAttempts  int                  `yaml:"retryAttempts"`
	RetryBackoff   RetryBackoffStrategy `yaml:"retryBackoff"`
}

19-27: Consider pointer consistency for optional fields.

The Post field uses a pointer (*PostConfig) to indicate it's optional, but other optional slice fields like Params, Preconditions, and Resources don't use pointers. For consistency and clarity about optionality, consider whether slices should remain as-is (empty slice = not provided) or if a consistent pattern should be applied.


46-61: Document mutually exclusive parameter configuration options.

The Parameter struct has multiple optional fields (Source, Build, BuildRef, FetchExternalResource) that appear to be mutually exclusive ways to configure a parameter. Add documentation to clarify:

  • When to use Source vs Build vs BuildRef vs FetchExternalResource
  • Whether combinations are valid
  • Which fields take precedence if multiple are set
// Parameter represents a parameter extraction configuration.
// A parameter can be configured in one of the following ways:
//   - Source: Extract from environment or event data (e.g., "env.VAR_NAME", "event.field")
//   - Build: Construct the parameter value using expressions and conditions
//   - BuildRef: Reference an external file containing build configuration
//   - FetchExternalResource: Fetch the parameter from a Kubernetes resource
// Only one configuration method should be specified per parameter.
type Parameter struct {
	Name        string      `yaml:"name"`
	Source      string      `yaml:"source,omitempty"`
	Type        string      `yaml:"type,omitempty"`
	Description string      `yaml:"description,omitempty"`
	Required    bool        `yaml:"required,omitempty"`
	Default     interface{} `yaml:"default,omitempty"`
	// For building parameters
	Build    *ParameterBuild    `yaml:"build,omitempty"`
	BuildRef string             `yaml:"buildRef,omitempty"`
	// BuildRefContent holds the loaded content from BuildRef file (populated by loader)
	BuildRefContent map[string]interface{} `yaml:"-"`
	// For fetching external resources
	FetchExternalResource *FetchExternalResource `yaml:"fetchExternalResource,omitempty"`
}

137-141: Add validation for Condition operators.

The Operator field is a string without constraints. Consider defining supported operators as constants or an enum to ensure type safety and enable validation.

type ConditionOperator string

const (
	OperatorEqual        ConditionOperator = "equals"
	OperatorNotEqual     ConditionOperator = "notEquals"
	OperatorIn           ConditionOperator = "in"
	OperatorNotIn        ConditionOperator = "notIn"
	OperatorExists       ConditionOperator = "exists"
	OperatorNotExists    ConditionOperator = "notExists"
	OperatorGreaterThan  ConditionOperator = "greaterThan"
	OperatorLessThan     ConditionOperator = "lessThan"
)

// Condition represents a structured condition
type Condition struct {
	Field    string            `yaml:"field"`
	Operator ConditionOperator `yaml:"operator"`
	Value    interface{}       `yaml:"value"`
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8c7587 and 53abfb6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_e2e_test.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/config_loader/loader_e2e_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • internal/criteria/evaluator_test.go
  • test/integration/config-loader/config_criteria_integration_test.go
  • internal/config_loader/loader_test.go
  • internal/criteria/types.go
  • internal/hyperfleet_api/client_test.go
  • configs/templates/deployment.yaml
  • internal/hyperfleet_api/README.md
  • internal/config_loader/README.md
  • internal/config_loader/validator.go
  • internal/criteria/evaluator_scenarios_test.go
  • configs/README.md
  • internal/config_loader/loader.go
  • internal/criteria/README.md
  • test/testdata/templates/deployment.yaml
  • internal/criteria/evaluator.go
🧰 Additional context used
🧬 Code graph analysis (7)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-61)
  • Resource (144-151)
  • Precondition (104-111)
  • PostAction (173-177)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/validator_test.go (3)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/config_loader/validator.go (1)
  • ValidationErrors (29-31)
internal/criteria/cel_evaluator.go (2)
internal/criteria/types.go (2)
  • EvaluationContext (61-64)
  • Operator (8-8)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
cmd/adapter/main.go (4)
internal/config_loader/loader.go (3)
  • EnvConfigPath (22-22)
  • Load (85-108)
  • WithAdapterVersion (52-56)
internal/config_loader/types.go (2)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
internal/hyperfleet_api/client.go (7)
  • BaseURLFromEnv (121-123)
  • EnvBaseURL (21-21)
  • ClientOption (36-36)
  • WithTimeout (65-69)
  • WithRetryAttempts (72-76)
  • WithRetryBackoff (79-83)
  • NewClient (100-118)
internal/hyperfleet_api/types.go (5)
  • Client (214-232)
  • BackoffStrategy (13-13)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
internal/hyperfleet_api/client.go (1)
internal/hyperfleet_api/types.go (10)
  • Client (214-232)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (54-63)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (34-51)
  • ConditionToCEL (205-241)
  • ConditionsToCEL (313-328)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
🔇 Additional comments (45)
test/integration/k8s_client/helper_envtest_prebuilt.go (2)

42-51: Cleanup implementation looks good.

The 30-second timeout and idempotent Terminate() call with error logging (without failing) is appropriate for cleanup operations. This aligns with the broader test infrastructure improvements in this PR.


135-147: Well-structured failure-path cleanup pattern.

The setupSuccess flag idiom ensures the container is cleaned up if any assertion or setup step fails before successful return. This prevents resource leaks on partial setup failures.

Also applies to: 186-187

test/integration/testutil/container.go (3)

156-169: Good leak prevention on retry path.

Terminating partially-created containers before retry prevents resource leaks. The fallback to forceCleanupContainer when Terminate() fails adds resilience.


178-203: Improved cleanup robustness with fallback.

Capturing containerID early ensures reliable cleanup even if the container object becomes unavailable. The 60-second minimum timeout and force cleanup fallback improve reliability.


236-261: Force cleanup fallback implementation addresses prior feedback.

The simplified error logging follows the suggestion from the previous review. The approach of trying docker then podman with informative warnings is appropriate for a fallback cleanup path.

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

123-128: LGTM!

The new imageTag parameter is well-structured with sensible defaults (v1.0.0), appropriate type (string), and correctly marked as optional (required: false). This aligns with the deployment template usage in configs/templates/deployment.yaml.


384-388: Unable to verify the API client header handling due to repository access issues.

The repository cannot be cloned in the verification environment, preventing direct examination of internal/hyperfleet_api/client.go. While general API best practices dictate that clients should automatically inject Authorization (Bearer token) and Content-Type: application/json headers for JSON POST requests, I cannot confirm whether the HyperFleet adapter's specific implementation does this.

The original concern remains valid: without access to the actual client code, it's unclear whether:

  1. The API client automatically sets these headers for all requests
  2. Whether explicit header configuration is required for the reportClusterStatus action
  3. Whether there are any middleware or interceptors handling default headers

Recommendation: Verify the API client implementation directly by examining the referenced file or consulting the adapter's documentation/team.

OWNERS (1)

2-2: LGTM!

Quoting the numeric approver ID "86254860" ensures it's correctly parsed as a string rather than a number in YAML. This is proper practice for identifiers.

internal/config_loader/accessors.go (5)

12-18: LGTM!

Clean implementation of built-in variables with clear documentation. The static list approach is appropriate for well-known template/CEL variables.


31-71: LGTM!

Comprehensive variable aggregation covering all sources (built-ins, params, precondition extracts, post params, resource aliases). The nil-safety guard at line 34-36 is appropriate.


74-84: LGTM!

Correct use of index-based iteration to return a pointer to the actual slice element rather than a copy.


167-174: LGTM!

The ParseTimeout method correctly handles empty timeout strings by returning (0, nil), allowing callers to apply default values. The nil-receiver check provides safety.


223-238: LGTM!

The UnmarshalManifest method has clear semantics: nil, nil for nil/empty input, map, nil for valid map, and nil, error for type mismatches. The documentation accurately describes the behavior.

internal/criteria/cel_evaluator_test.go (4)

12-20: LGTM!

Good basic test validating evaluator construction with a populated context.


22-113: LGTM!

Excellent table-driven test coverage including string equality, numeric comparisons, boolean variables, compound expressions, list membership, empty expressions, and invalid syntax handling. The test structure cleanly separates expected values from error cases.


177-283: LGTM!

Comprehensive operator coverage including equals, notEquals, greaterThan, lessThan, in, notIn, contains, and exists (both nested and top-level variants). The invalid operator error case is also tested.


355-387: LGTM!

Good edge case coverage for value formatting, including nil, strings with quotes, booleans, numbers, slices, and unsupported types (channel, map, func). Testing time.Duration as int64 (line 370) correctly verifies the underlying type handling.

internal/hyperfleet_api/types.go (5)

15-22: LGTM!

Clear backoff strategy definitions with documented behavior for each strategy type.


53-63: LGTM!

The DefaultClientConfig constructor properly initializes all fields with sensible defaults and creates an empty map for DefaultHeaders to avoid nil map writes.


69-85: LGTM!

Using pointer types for RetryAttempts and RetryBackoff allows distinguishing between "not set" (nil) and "explicitly set to zero/default" - enabling clean per-request overrides while falling back to client defaults.


186-199: LGTM!

The IsRetryable logic correctly identifies retryable HTTP status codes: specific client errors (408 Request Timeout, 429 Too Many Requests) and gateway/server errors (502, 503, 504, plus all 5xx). This follows standard retry patterns.


213-232: LGTM!

Clean interface design with context-aware method signatures enabling proper timeout/cancellation propagation. The functional options pattern provides flexible request customization.

configs/broker-configmap-pubsub-template.yaml (1)

24-28: Unfortunately, I'm unable to clone the repository to verify the review comment. The repository clone operation failed, which prevents me from:

  1. Examining the actual configs/broker-configmap-pubsub-template.yaml file to confirm the changes
  2. Searching for references to the old key names (SUBSCRIPTION_ID, TOPIC) throughout the codebase
  3. Identifying any consumers (Go code, deployments, etc.) that may still reference the old key names
  4. Verifying the completeness of the rename across the entire project

Given this limitation, I cannot definitively verify whether the original review comment's concern about "ensuring any existing deployments or code referencing the old key names are updated accordingly" has been addressed.


Cannot verify due to repository access failure — manual review recommended.

The review requests verification that existing consumers of the old key names (SUBSCRIPTION_ID, TOPIC) are updated to use the new BROKER_SUBSCRIPTION_ID and BROKER_TOPIC names. This verification requires access to search the full codebase for references to these configuration keys.

configs/templates/cluster-status-payload.yaml (1)

1-16: LGTM! Template structure is well-designed.

The template uses appropriate defaults throughout to prevent missing-field issues, and the ISO8601 date format is correctly applied.

cmd/adapter/main.go (3)

30-35: LGTM! CLI flag properly configured.

The --config flag is well-integrated with a clear usage message that references the environment variable fallback.


62-91: LGTM! Configuration loading and API client initialization are well-structured.

The startup sequence properly:

  • Loads adapter configuration with version validation
  • Verifies required environment variables
  • Creates the API client before subscribing to events
  • Provides clear error messages at each step

188-218: LGTM! Client creation helper is well-implemented.

The function properly:

  • Parses and validates timeout duration
  • Configures retry attempts when specified
  • Validates backoff strategy against supported values
  • Provides clear error messages with context
go.mod (1)

5-20: LGTM! Dependencies align well with new features.

The added dependencies (CEL, YAML, Docker tooling) appropriately support the config loader, criteria evaluator, and integration tests introduced in this PR.

internal/config_loader/validator_test.go (1)

1-679: LGTM! Comprehensive test coverage for configuration validation.

The test suite thoroughly exercises:

  • Operator validation (valid, invalid, missing)
  • Template variable validation (defined, undefined, nested)
  • CEL expression validation (syntax, functions)
  • Kubernetes manifest validation (required fields)
  • Error formatting and aggregation

The table-driven approach provides good coverage of edge cases.

pkg/errors/api_error.go (1)

14-153: LGTM! APIError type is well-designed and comprehensive.

The implementation provides:

  • Complete error context (method, URL, status, attempts, duration)
  • Proper error wrapping with Unwrap() for errors.Is/As support
  • Convenient helper methods for status code classification
  • Clear error messages with conditional formatting
  • Type-safe assertion helpers

This will enable robust error handling throughout the API client layer.

internal/config_loader/validator_schema.go (1)

16-502: LGTM! SchemaValidator is well-architected.

The implementation provides:

  • Clean separation of structural validation, file validation, and file loading
  • Fail-fast validation with clear error paths
  • Proper file path resolution (absolute/relative handling)
  • Comprehensive validation of required fields and constraints
  • Backward-compatible legacy wrappers

The resource discovery validation (lines 164-190) correctly ensures resources can be located on subsequent messages.

internal/criteria/cel_evaluator.go (3)

34-93: LGTM! CEL environment setup is well-implemented.

The evaluator correctly:

  • Enables OptionalTypes() for null-safe field access
  • Dynamically registers variables from the evaluation context
  • Infers appropriate CEL types for Go values
  • Handles nil values with DynType

95-149: LGTM! Evaluation logic is robust and well-designed.

The Evaluate method properly:

  • Handles empty expressions gracefully
  • Performs parsing and type-checking with fallback to parsed AST
  • Logs type check issues at appropriate verbosity level
  • Determines Matched status for both boolean and non-boolean results

312-328: LGTM! Multi-condition conversion is correctly implemented.

The function properly converts multiple conditions into a single CEL expression with AND logic, using parentheses to ensure correct precedence.

internal/hyperfleet_api/client.go (4)

38-118: LGTM! Client construction follows functional options pattern correctly.

The implementation provides flexible configuration through:

  • Individual option setters for each configuration aspect
  • Default configuration fallback
  • Automatic HTTP client creation with timeout

139-215: LGTM! Retry logic is well-implemented.

The Do method properly:

  • Supports per-request retry configuration overrides
  • Checks context cancellation before each attempt
  • Distinguishes between success and retryable errors
  • Calculates appropriate backoff delays
  • Returns comprehensive APIError with full context when retries are exhausted

217-312: LGTM! Request execution and backoff calculation are well-designed.

The implementation correctly:

  • Handles per-request timeout overrides
  • Sets appropriate default headers (Content-Type for body requests)
  • Implements three backoff strategies with jitter (±10%) to prevent thundering herd
  • Caps delays at maxDelay to prevent excessive waits
  • Provides appropriate logging at different verbosity levels

318-379: LGTM! Convenience methods provide clean API surface.

The HTTP method wrappers correctly construct requests and delegate to the main Do method, maintaining support for per-request options.

test/testdata/adapter_config_valid.yaml (6)

24-51: LGTM!

The parameters section is well-structured with clear sources (environment variables and event data), appropriate types, and required flags. The inclusion of defaults where necessary (e.g., hyperfleetApiVersion) is good practice.


87-88: LGTM!

The CEL expression is syntactically correct and provides a clear validation check for cluster phase states.


90-134: LGTM! Verify external template file path.

The resources section is well-structured with proper Kubernetes manifest definitions and discovery configurations. The use of namespace: "*" for the cluster-scoped Namespace resource is correct.

Note that the externalTemplate resource references "templates/deployment.yaml" (line 128). Ensure this file exists and is properly loaded during configuration validation.


135-202: LGTM!

The post-processing configuration is comprehensive and well-structured:

  • Condition builds for applied/available/health states with appropriate CEL expressions
  • Data fields with descriptive metadata
  • Post action with proper API call configuration and authentication headers
  • Conditional execution using CEL expression

The CEL expressions correctly reference the resources created in earlier stages and implement proper null-safety checks.


18-19: Repository access unavailable for verification.

Unable to verify the review comment due to repository access issues. The repository openshift-hyperfleet/hyperfleet-adapter could not be cloned, and no public documentation for the hyperfleet-adapter configuration or its supported retry backoff strategies is available through web search or GitHub API.

To properly verify that "exponential" is a supported retry backoff strategy, one of the following is needed:

  • Direct access to the codebase configuration validation logic
  • Schema definition or documentation for supported backoff strategy values
  • Related configuration files or test cases demonstrating valid backoff values

Without access to this information, the original concern cannot be definitively confirmed or refuted.


81-83: Based on the web search results and the standard patterns for the "exists" operator across systems (SQL, Drools, Elasticsearch), the review concern is valid. The "exists" operator is typically a unary operator that checks field presence and does not take a value parameter.

However, since I cannot access the repository to inspect the actual implementation of the criteria evaluator, I cannot definitively determine whether:

  1. The hyperfleet-adapter's implementation handles value: true with "exists" correctly (and the value is used or ignored)
  2. This is a redundant field that's accepted but unused
  3. This is an error in the test data

Rewritten review comment:


Verify "exists" operator handling with boolean value.

The condition at lines 81-83 uses operator: "exists" with value: true. According to standard criteria evaluation patterns (SQL, Drools, Elasticsearch), "exists" is typically a unary operator that checks field presence without requiring a value parameter. Confirm that the hyperfleet-adapter's criteria evaluator correctly handles this combination—either by validating that the value field is properly processed or by removing the redundant value field if the operator doesn't consume it.


internal/config_loader/types.go (1)

142-189: LGTM!

The resource and post-processing types are well-designed:

  • Resource.Manifest as interface{} appropriately handles various manifest formats (inline maps, file references, arrays)
  • ManifestItems with yaml:"-" correctly hides internal loader state from serialization
  • DiscoveryConfig provides flexible resource discovery options
  • WhenExpr supports both CEL expressions and structured conditions

The type definitions align well with the test configuration file structure.

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

♻️ Duplicate comments (2)
test/integration/testutil/container.go (1)

236-261: Previous review feedback has been addressed.

The string matching for "No such container" has been removed. The function now logs all errors, which is appropriate for a best-effort cleanup path.

internal/criteria/cel_evaluator.go (1)

249-253: String escaping is incomplete for special characters.

The current implementation only escapes double quotes. Other special characters like backslashes, newlines, tabs, and control characters are not handled, which can produce invalid CEL string literals.

Use strconv.Quote for proper escaping:

 	case string:
-		// Escape quotes in string
-		escaped := strings.ReplaceAll(v, `"`, `\"`)
-		return fmt.Sprintf(`"%s"`, escaped), nil
+		// Use strconv.Quote for proper CEL string literal escaping
+		return strconv.Quote(v), nil

This ensures all special characters (backslash, quotes, newlines, tabs, etc.) are properly escaped.

🧹 Nitpick comments (11)
test/integration/k8s_client/helper_testcontainers_k3s.go (1)

96-106: Good pattern for preventing container leaks, but inner nil check is redundant.

Registering cleanup immediately after creation ensures the container is terminated even if subsequent require assertions fail. However, the nil check at line 100 is redundant since the cleanup block is only entered when k3sContainer != nil (line 98).

 	// Register cleanup immediately after creation to prevent leaks if assertions fail
 	if k3sContainer != nil {
 		t.Cleanup(func() {
-			if k3sContainer != nil {
-				ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-				defer cancel()
-				_ = k3sContainer.Terminate(ctx)
-			}
+			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+			defer cancel()
+			_ = k3sContainer.Terminate(ctx)
 		})
 	}
configs/templates/deployment.yaml (1)

5-36: Deployment template looks good; parameterization options are optional.

The deployment spec and templating of clusterId, resourceId, and imageTag are consistent and valid. If you expect this to be reused across environments, you might optionally expose resources/imagePullPolicy as template inputs rather than hard-coding them, but it’s not required for correctness.

internal/config_loader/accessors.go (2)

24-68: Guard against empty variable names in GetDefinedVariables().

GetDefinedVariables currently adds entries even when ExtractField.As or Resource.Name are empty, which can introduce meaningless keys like "" or "resources." into the map. That’s harmless but can confuse validation/debugging.

Consider tightening the checks:

@@ func (c *AdapterConfig) GetDefinedVariables() map[string]bool {
-    // Variables from precondition extracts
-    for _, precond := range c.Spec.Preconditions {
-        if precond.StoreResponseAs != "" {
-            vars[precond.StoreResponseAs] = true
-        }
-        for _, extract := range precond.Extract {
-            vars[extract.As] = true
-        }
-    }
+    // Variables from precondition extracts
+    for _, precond := range c.Spec.Preconditions {
+        if precond.StoreResponseAs != "" {
+            vars[precond.StoreResponseAs] = true
+        }
+        for _, extract := range precond.Extract {
+            if extract.As != "" {
+                vars[extract.As] = true
+            }
+        }
+    }
@@ func (c *AdapterConfig) GetDefinedVariables() map[string]bool {
-    // Resource aliases
-    for _, r := range c.Spec.Resources {
-        vars["resources."+r.Name] = true
-    }
+    // Resource aliases
+    for _, r := range c.Spec.Resources {
+        if r.Name != "" {
+            vars["resources."+r.Name] = true
+        }
+    }

You could apply a similar guard for empty parameter names if you expect those to ever be optional.


180-238: Manifest helpers are safe but a bit strict on map types.

HasManifestRef, GetManifestRefs, and UnmarshalManifest all require r.Manifest to be map[string]interface{}; anything else (e.g., a different map key type from YAML decoding) will be treated as “no manifest” or cause an error.

If there’s any chance your loader could still produce map[interface{}]interface{} or other map forms, you might want to add a normalization step (e.g., converting map keys to string) before these helpers run, or extend these functions to handle that case. If loader already guarantees map[string]interface{}, then current behavior is fine and just worth keeping in mind.

internal/config_loader/types.go (1)

63-69: Confirm mixed YAML casing in ParameterBuild is intentional.

ParameterBuild uses observed_generation for ObservedGeneration and lastUpdated for LastUpdated. If that’s by design (e.g., to mirror upstream field names), all good; otherwise, this mixed style is easy for config authors to mistype and hard to validate.

It’s worth double-checking that the template docs and validator_schema expect exactly these field names before this schema becomes widely used.

internal/config_loader/loader_e2e_test.go (1)

12-105: E2E loader tests provide good coverage of the real template.

Anchoring test data paths via runtime.Caller and validating key fields (metadata, HyperFleet API config, params, preconditions, resources, post config) gives strong confidence the loader works against the actual shipped templates. The ../.. jump assumes this file stays under internal/config_loader/, which is acceptable but worth remembering if the package layout ever changes.

cmd/adapter/main.go (2)

143-161: Avoid logging full event payload at info level.

log.Infof("Received event: ... data=%s", string(evt.Data())) will emit the entire event body for every message, which can be both noisy at scale and risky if payloads contain sensitive data.

Consider logging only metadata (id/type/source) at info level and gating the payload behind a debug/trace flag or truncating it, e.g.:

log.Infof("Received event: id=%s type=%s source=%s", evt.ID(), evt.Type(), evt.Source())
// log.Debugf("Event payload: %s", string(evt.Data()))

188-218: Reuse HyperfleetAPIConfig.ParseTimeout in createAPIClient for consistency.

createAPIClient reimplements timeout parsing instead of using HyperfleetAPIConfig.ParseTimeout(), which is already defined in config_loader/accessors.go. Reusing it keeps the behavior in one place:

func createAPIClient(apiConfig config_loader.HyperfleetAPIConfig) (hyperfleet_api.Client, error) {
    var opts []hyperfleet_api.ClientOption

-   // Parse and set timeout
-   if apiConfig.Timeout != "" {
-       timeout, err := time.ParseDuration(apiConfig.Timeout)
-       if err != nil {
-           return nil, fmt.Errorf("invalid timeout %q: %w", apiConfig.Timeout, err)
-       }
-       opts = append(opts, hyperfleet_api.WithTimeout(timeout))
-   }
+   // Parse and set timeout using shared accessor
+   if timeout, err := apiConfig.ParseTimeout(); err != nil {
+       return nil, fmt.Errorf("invalid timeout %q: %w", apiConfig.Timeout, err)
+   } else if timeout > 0 {
+       opts = append(opts, hyperfleet_api.WithTimeout(timeout))
+   }

    // retry attempts and backoff handling unchanged...

This avoids divergence if timeout parsing rules ever change.

internal/criteria/evaluator_scenarios_test.go (1)

220-299: Consider isolating context per subtest for future-proofing.

The shared EvaluationContext across parameterized subtests works correctly since subtests run sequentially. However, if t.Parallel() is ever added, this would introduce a data race.

For added safety, consider creating a fresh context per subtest:

 for _, tt := range tests {
     t.Run(tt.name, func(t *testing.T) {
+        ctx := NewEvaluationContext()
+        evaluator := NewEvaluator(ctx)
         ctx.Set("nodeCount", tt.nodeCount)
         ctx.Set("minNodes", tt.minNodes)
         ctx.Set("maxNodes", tt.maxNodes)
internal/config_loader/validator_schema.go (1)

237-242: Consider using a pre-built constant for valid HTTP methods in error messages.

The current implementation builds the method list dynamically each time an invalid method is encountered. Since ValidHTTPMethods is a constant map, you could define a companion constant slice of method names to avoid rebuilding the list.

Example refactor in loader.go:

var ValidHTTPMethodsList = []string{"GET", "POST", "PUT", "PATCH", "DELETE"}

Then simplify the error message:

-	if _, valid := ValidHTTPMethods[apiCall.Method]; !valid {
-		methods := make([]string, 0, len(ValidHTTPMethods))
-		for m := range ValidHTTPMethods {
-			methods = append(methods, m)
-		}
-		return fmt.Errorf("%s.method %q is invalid (allowed: %s)", path, apiCall.Method, strings.Join(methods, ", "))
-	}
+	if _, valid := ValidHTTPMethods[apiCall.Method]; !valid {
+		return fmt.Errorf("%s.method %q is invalid (allowed: %s)", path, apiCall.Method, strings.Join(ValidHTTPMethodsList, ", "))
+	}
internal/hyperfleet_api/client.go (1)

302-304: Consider using a seeded random source for jitter calculation.

The current implementation uses rand.Float64() which relies on the global random source. While this is automatically seeded in Go 1.20+, using an explicit seeded source improves testability and makes behavior more predictable.

Consider initializing a seeded random source in the httpClient struct:

type httpClient struct {
    client *http.Client
    config *ClientConfig
    rand   *rand.Rand  // Add seeded random source
}

Initialize in NewClient:

c := &httpClient{
    config: DefaultClientConfig(),
    rand:   rand.New(rand.NewSource(time.Now().UnixNano())),
}

Then use c.rand.Float64() instead of rand.Float64() in calculateBackoff.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53abfb6 and 6174f0e.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_e2e_test.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/criteria/README.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • internal/hyperfleet_api/README.md
  • configs/adapter-config-template.yaml
  • configs/templates/cluster-status-payload.yaml
  • configs/README.md
  • internal/config_loader/loader_test.go
  • OWNERS
  • configs/templates/job.yaml
🧰 Additional context used
🧬 Code graph analysis (12)
internal/config_loader/loader_e2e_test.go (3)
internal/config_loader/loader.go (1)
  • Load (85-108)
internal/config_loader/types.go (3)
  • Metadata (12-16)
  • APICall (114-122)
  • ExtractField (131-134)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (114-122)
  • Resource (144-151)
  • SelectorConfig (161-164)
  • Parameter (47-61)
  • FetchExternalResource (96-101)
  • Precondition (104-111)
internal/config_loader/loader.go (3)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
internal/hyperfleet_api/client.go (3)
internal/hyperfleet_api/types.go (10)
  • Client (214-232)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (54-63)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
pkg/errors/error.go (1)
  • New (107-123)
pkg/errors/api_error.go (1)
  • NewAPIError (121-132)
test/integration/config-loader/config_criteria_integration_test.go (4)
internal/config_loader/loader.go (1)
  • Load (85-108)
internal/criteria/types.go (4)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorExists (26-26)
  • OperatorGreaterThan (22-22)
internal/criteria/evaluator.go (2)
  • NewEvaluator (41-48)
  • ConditionDef (271-275)
internal/criteria/cel_evaluator.go (2)
  • ConditionsToCEL (313-328)
  • ConditionToCEL (205-241)
internal/config_loader/validator.go (4)
pkg/errors/error.go (1)
  • Errors (77-95)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (114-122)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (41-48)
  • ConditionDef (271-275)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (450-462)
internal/config_loader/validator.go (1)
  • Validate (470-472)
internal/hyperfleet_api/client_test.go (3)
internal/hyperfleet_api/client.go (9)
  • NewClient (100-118)
  • ClientOption (36-36)
  • WithTimeout (65-69)
  • WithRetryAttempts (72-76)
  • WithRetryBackoff (79-83)
  • WithBaseDelay (86-90)
  • WithMaxDelay (93-97)
  • WithDefaultHeader (55-62)
  • WithConfig (46-52)
internal/hyperfleet_api/types.go (5)
  • ClientConfig (38-51)
  • Request (70-85)
  • WithHeader (103-110)
  • DefaultClientConfig (54-63)
  • Response (156-169)
pkg/errors/api_error.go (2)
  • NewAPIError (121-132)
  • IsAPIError (142-148)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-61)
  • Resource (144-151)
  • Precondition (104-111)
  • PostAction (173-177)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/validator_test.go (3)
internal/config_loader/loader.go (1)
  • Parse (111-127)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/config_loader/validator.go (1)
  • ValidationErrors (29-31)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
🔇 Additional comments (46)
test/integration/k8s_client/helper_testcontainers_k3s.go (1)

159-171: Double-termination may occur but is safe.

The t.Cleanup registered at line 99 and this explicit Cleanup() method both terminate the same container. When tests call defer env.Cleanup(t), the container may be terminated twice. This is safe since Terminate() is idempotent, but the warning at line 168 will likely fire on the second call with "container already stopped" or similar.

Consider tracking termination state if you want to avoid the noise, or document that double-cleanup is expected and benign.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

135-147: Setup failure cleanup pattern is well-implemented.

The setupSuccess flag pattern ensures the container is terminated immediately if setup fails (before assertions), while the existing t.Cleanup from StartContainer handles normal test completion. Since Terminate() is idempotent, the potential double-call is safe.


40-51: LGTM!

The cleanup now gracefully handles termination failures by logging instead of failing the test. The comment about idempotency is helpful documentation.

test/integration/testutil/container.go (3)

156-169: Good addition of cleanup for partially-created containers.

When a container is created but the wait strategy times out, this cleanup prevents leaks by terminating before retry. The fallback to forceCleanupContainer is a sensible safety net.


177-204: Well-designed cleanup with captured container identity and fallback.

Capturing containerID and containerName outside the closure ensures the correct container is targeted during cleanup. The extended timeout (minimum 60s) and force-cleanup fallback provide resilience against slow container runtimes.


263-298: LGTM!

The CleanupLeakedContainers function correctly uses the ancestor filter to find containers by image and cleans them up. The early return after success with one runtime prevents redundant cleanup attempts.

configs/broker-configmap-pubsub-template.yaml (1)

24-29: BROKER_ key rename is consistent within this template.*

The switch to BROKER_SUBSCRIPTION_ID and BROKER_TOPIC is applied coherently in both the ConfigMap data and the commented Deployment example, which keeps the broker-specific settings clearly namespaced.

Also applies to: 93-97

test/testdata/templates/deployment.yaml (1)

5-26: Test deployment template is consistent and usable as fixture.

The templating of clusterId and use of .metadata.name for the managed-by label line up with the config model and give a realistic manifest for loader/validator tests. No issues from a test-data perspective.

internal/config_loader/README.md (1)

20-261: README aligns well with the current config_loader API and schema.

Usage examples (Load/Parse, accessors, ParseTimeout) and the YAML snippets for params, preconditions, resources, and post-processing match the Go types in types.go and the template file. This should be very helpful for consumers of the package.

go.mod (1)

1-143: Dependencies align with the new CEL and config loading functionality.

The added dependencies (cel-go, antlr4-go, stoewer/go-strcase, golang.org/x/exp) are appropriate for CEL expression evaluation. The gopkg.in/yaml.v3 v3.0.1 version is the latest available through the gopkg.in proxy as previously discussed.

internal/hyperfleet_api/client_test.go (5)

1-93: Comprehensive client construction tests.

Good coverage of the client option patterns including timeout, retry attempts, backoff strategies, and custom headers.


194-335: Well-designed retry and timeout tests.

Good use of atomic.Int32 for thread-safe request counting in retry tests. The timeout values (100ms, 50ms) are appropriately small for tests while still demonstrating the behavior.


378-418: Backoff tests correctly account for jitter.

The 20% tolerance range appropriately handles the inherent randomness in jitter-based backoff calculations while still verifying the expected delay magnitudes.


572-594: Good coverage of error unwrapping.

The test correctly verifies that IsAPIError works with both direct APIError instances and wrapped errors using fmt.Errorf("wrapped: %w", ...), ensuring compatibility with Go's standard error wrapping patterns.


596-640: Excellent integration test for retry exhaustion with APIError.

This test validates the end-to-end flow of retry exhaustion, ensuring that the returned error contains proper context (method, status code, attempts, response body) that can be extracted via IsAPIError.

internal/criteria/evaluator_scenarios_test.go (1)

10-89: Realistic precondition evaluation scenario.

The test effectively simulates a cluster details response with nested data structures and validates the precondition evaluation logic using operators like In and Exists.

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

1-14: Proper use of build tag for integration tests.

The //go:build integration tag ensures these tests only run when explicitly requested, which is appropriate given their dependency on external config files.


16-96: Comprehensive precondition evaluation integration test.

Good coverage of loading actual config templates and evaluating preconditions with simulated runtime data. The test validates both individual condition evaluation and combined expression evaluation.


340-395: Thorough null safety testing.

Excellent coverage of edge cases including partially populated resources, null values at different nesting levels, and verification that safe accessors return appropriate defaults.


417-426: Parameter source validation correctly uses strings.HasPrefix.

The fix from the previous review is properly applied, avoiding the potential panic from unsafe slice access.

internal/criteria/evaluator_test.go (4)

1-111: Solid foundation tests for EvaluationContext.

Good coverage of context lifecycle including creation, set/get operations, nested field access, and context merging with proper overwrite semantics.


113-426: Comprehensive operator evaluation tests.

Excellent coverage of all operators with both positive and negative test cases, including edge cases like nil values, empty collections, and type mismatches.


631-665: Thorough numeric type conversion tests.

The toFloat64 tests cover all Go numeric types (int, int8-64, uint, uint8-64, float32, float64) ensuring the conversion function handles the full range of numeric inputs.


891-979: Excellent null handling test coverage.

The tests comprehensively verify null safety at various nesting levels, including:

  • Null parent fields
  • Safe accessors returning nil
  • Default value fallbacks
  • HasField returning false for null paths
  • Safe evaluation returning false for null paths

This ensures robust handling of partially populated or incomplete data structures.

internal/criteria/cel_evaluator.go (1)

230-237: The code comment is misleading about has() null-safety with OptionalTypes.

Based on CEL documentation, has() is a macro for presence testing of fields/keys, not for null-safe nested field access. OptionalTypes does not make has() safer for intermediate null values. Instead, OptionalTypes introduces a separate optional-selection syntax (obj.?field) that is null-safe and prevents errors from missing intermediate fields.

The comment "has() safely handles null intermediates" suggests has(a.b.c) is protected by OptionalTypes, but this is incorrect. Without explicit optional selection syntax (a.?b.?c), nested has() calls can still fail if intermediate fields are null.

For the exists operator with nested fields (lines 234-236), the current implementation using has() directly on nested paths may not be safe even with OptionalTypes enabled. To properly handle null intermediates, the code should either:

  1. Use optional selection syntax if available in the CEL evaluator, or
  2. Maintain the explicit null checks for intermediate levels as a safer fallback

Update the comment to accurately describe the current behavior or refactor the implementation to use null-safe optional selection syntax if the CEL environment supports it.

Likely an incorrect or invalid review comment.

internal/config_loader/loader.go (4)

15-36: LGTM! Well-organized constants.

The API versioning, supported versions list, and HTTP method whitelist are clearly defined and provide good foundation for validation.


42-70: LGTM! Clean functional options pattern.

The options API provides flexibility for version validation, semantic validation control, and base directory configuration. The pattern is correctly implemented with loaderConfig always initialized before options are applied.


76-139: LGTM! Robust loader implementation.

The Load and Parse functions provide a clean API with proper error handling:

  • Automatic config path detection from environment
  • Absolute path derivation for relative reference resolution
  • Clear separation between parsing and validation
  • Deprecated wrappers maintain backward compatibility

145-195: I'm unable to proceed with the verification because the repository clone operation failed. This typically occurs when:

  • The repository is private and requires authentication credentials
  • The repository URL or name is incorrect
  • Network restrictions prevent access in the sandbox environment

Since I cannot access the codebase to verify that all validator functions are properly defined in validator_schema.go, I cannot confirm whether the review comment's verification request yields valid results.

LGTM! Well-structured validation pipeline.

The multi-stage validation approach is clean and flexible:

  • Core structural validators execute unconditionally
  • Adapter version validation is optional
  • File reference validation/loading only runs when baseDir is set (appropriate since refs are only resolvable from file-based configs)
  • Semantic validation can be skipped for performance

The verification request to confirm all core validator functions are defined in validator_schema.go requires manual inspection:

  • validateAPIVersionAndKind
  • validateMetadata
  • validateAdapterSpec
  • validateParams
  • validatePreconditions
  • validateResources
  • validatePostActions
  • validateFileReferences
  • loadFileReferences
internal/config_loader/validator.go (6)

18-96: LGTM! Clean validation framework.

The validation error aggregation and orchestration are well-designed:

  • ValidationErrors provides clear multi-error reporting with path context
  • Validator.Validate() orchestrates all semantic checks systematically
  • CEL environment initialization failure is captured but doesn't halt validation (allows reporting all errors at once)

102-144: LGTM! Comprehensive operator validation.

The operator validation covers all condition locations (preconditions and post action gates) with clear path context. Integration with criteria.IsValidOperator() ensures consistent operator validation across the codebase.


150-275: LGTM! Thorough template variable validation.

The template validation is comprehensive and handles edge cases well:

  • Regex pattern correctly matches Go template syntax including filters
  • Validates templates across all usage locations (API calls, resources, discovery, post-processing)
  • Nested path handling is smart—validates root variable for paths like clusterDetails.status.phase
  • Recursive map validation ensures deep template checking

281-396: LGTM! Robust CEL validation with appropriate error handling.

The CEL validation implementation is excellent:

  • Dynamic environment construction from config-defined variables with deduplication optimization
  • DynType usage is appropriate for validation time (actual types unknown)
  • Optional types enabled for null-safe field access
  • Comprehensive expression coverage across all config sections
  • Smart error handling: parse errors fail validation, but type errors are logged only (since dynamic variables may not type-check at validation time)

402-461: LGTM! Appropriate Kubernetes manifest validation.

The manifest validation provides essential structural checks:

  • Required K8s fields (apiVersion, kind, metadata)
  • Metadata name requirement
  • Proper handling of external template references (ref/refs)
  • Empty value checks for critical fields

The validation level is appropriate for config loading—deeper validation would require API server interaction.


467-472: LGTM! Clean backward-compatible API.

The public Validate function provides a simple entry point while maintaining backward compatibility.

test/testdata/adapter_config_valid.yaml (3)

16-50: LGTM! Comprehensive parameter configuration.

The configuration demonstrates:

  • HyperFleet API retry semantics (retryAttempts, retryBackoff)
  • Clear parameter definitions with explicit types and required flags
  • Mix of environment and event-based parameter sources
  • Good coverage for testing parameter extraction logic

52-134: LGTM! Well-structured preconditions and resources.

The configuration provides excellent test coverage:

  • Precondition with API call, field extraction, structured conditions, and CEL expression
  • Resources with both inline manifests and external template references
  • Discovery configurations covering namespace, name-based, and selector-based lookups
  • Template variables consistently reference defined parameters and extracted fields

136-202: LGTM! Comprehensive post-processing configuration.

The post-processing section demonstrates:

  • Status payload building with condition expressions accessing resource state
  • Null-safe CEL expressions using has() checks
  • Template usage for metadata fields (observed_generation, lastUpdated)
  • Post action with complete API call configuration including headers and body
  • Conditional gate ensuring payload availability before reporting

This provides excellent test coverage for the post-processing pipeline.

internal/criteria/evaluator.go (8)

9-48: LGTM! Well-designed evaluation framework.

The result types provide comprehensive evaluation information:

  • Individual evaluation results with matched status, actual and expected values
  • Aggregate results with failure tracking and extracted fields
  • Defensive nil context handling in constructor

50-74: LGTM! Flexible field access API.

The field access methods provide multiple convenience patterns:

  • Standard error-returning access
  • Default value fallback
  • Null-safe access (no error)
  • Existence checking

This API design accommodates different error handling preferences.


76-191: LGTM! Comprehensive condition evaluation with multiple API styles.

The condition evaluation provides:

  • Backward-compatible boolean API
  • Null-safe variant for defensive programming
  • Detailed result API for rich error reporting
  • Multi-condition evaluation with AND logic and failure tracking
  • Proper operator dispatching with validation

Error handling is appropriate, especially the null-safe variant's treatment of missing fields.


193-268: LGTM! Rich CEL integration.

The CEL integration provides:

  • Direct CEL expression evaluation
  • Type-specific evaluation helpers (Bool, String)
  • Seamless condition-to-CEL conversion
  • CEL expression generation from conditions

This enables flexible evaluation strategies (native vs. CEL-based).


277-371: LGTM! Robust operator implementations.

The operator implementations handle edge cases well:

  • Null/nil handling in equality checks
  • Type validation for list operations
  • Dual-mode contains (string substring + collection membership)
  • Comprehensive existence checking (nil, empty values, zero-length collections)

373-418: LGTM! Comprehensive numeric type handling.

The numeric comparison utilities provide:

  • Clean functional design with comparison function parameter
  • Exhaustive numeric type coverage (all signed/unsigned integer widths + floats)
  • Clear error messages for non-numeric types

420-513: LGTM! Robust nested field traversal.

The field traversal implementation is excellent:

  • Clean recursive design with depth tracking
  • Null-safe with clear error messages including path context
  • Dual map type support (string and interface{} keys)
  • Reflection-based struct access with pointer dereferencing
  • Case-insensitive struct field fallback (useful for API responses)

515-530: LGTM! Clear error signaling.

The FieldNotFoundError provides:

  • Rich context (path and field information)
  • Type checking helper for error discrimination
  • Clear error messages

This enables callers to distinguish field-not-found errors from other evaluation errors.

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: 3

♻️ Duplicate comments (4)
test/integration/testutil/container.go (1)

236-261: Previous feedback addressed: Simplified error logging.

The implementation now logs all errors without fragile string matching, accepting some "not found" noise as acceptable for cleanup paths. This follows the guidance from the past review.

internal/hyperfleet_api/client.go (1)

146-151: Normalize retryAttempts so at least one HTTP attempt is made

If c.config.RetryAttempts or *req.RetryAttempts is set to 0 or a negative value, the loop for attempt := 1; attempt <= retryAttempts; attempt++ never executes, so no HTTP request is sent and you only get an APIError with Attempts equal to that non-sensical value. This was already noted in an earlier review.

Normalizing to at least one attempt avoids the “zero-attempt” surprise while keeping configuration flexible:

	// Determine retry configuration
	retryAttempts := c.config.RetryAttempts
	if req.RetryAttempts != nil {
		retryAttempts = *req.RetryAttempts
	}
+	// Ensure at least one attempt is made
+	if retryAttempts < 1 {
+		glog.Warningf("HyperFleet API retryAttempts=%d is < 1, normalizing to 1", retryAttempts)
+		retryAttempts = 1
+	}

This guarantees that callers always get at least a single HTTP attempt, regardless of misconfigured retry values.

Also applies to: 161-217

internal/criteria/cel_evaluator_test.go (1)

177-283: Align exists expectations with safer CEL expressions once ConditionToCEL is fixed

The TestConditionToCEL cases for the exists operator currently assert has(...) for nested fields:

// examples
{field: "metadata.name", operator: "exists", want: `has(metadata.name)`},
{field: "cluster.status.phase", operator: "exists", want: `has(cluster.status.phase)`},
{field: "a.b.c.d", operator: "exists", want: `has(a.b.c.d)`},

Given the CEL semantics issues around has(a.b.c) on missing/null intermediates (called out in a previous review and in the ConditionToCEL comment), these expectations should be updated when you switch to an optional‑safe pattern like metadata.?name != null or cluster.?status.?phase != null.

Otherwise, the tests will lock in the unsafe behavior and make it harder to evolve the expression generation.

internal/criteria/cel_evaluator.go (1)

204-241: existshas(fieldPath) remains unsafe for nested fields; prefer optional-safe selection

The exists branch still emits has(fieldPath) for nested paths (e.g. cluster.status.phasehas(cluster.status.phase)):

case "exists":
	// CEL has() macro checks if a field exists on an object
	// With cel.OptionalTypes() enabled, has() safely handles null intermediates
	if strings.Contains(celField, ".") {
		return fmt.Sprintf("has(%s)", celField), nil
	}
	// For top-level variables, check not null and not empty string
	return fmt.Sprintf("(%s != null && %s != \"\")", celField, celField), nil

Per CEL semantics, has(a.b.c) still requires evaluating intermediate selectors; if a or a.b is absent/null, evaluation can fail rather than safely returning false. This was already highlighted in an earlier review.

Since you’re enabling cel.OptionalTypes(), you can generate an optional-safe selection chain instead, e.g. cluster.?status.?phase != null, which avoids touching deeper selectors when parents are missing:

 case "exists":
-	// CEL has() macro checks if a field exists on an object
-	// With cel.OptionalTypes() enabled, has() safely handles null intermediates
-	if strings.Contains(celField, ".") {
-		return fmt.Sprintf("has(%s)", celField), nil
-	}
-	// For top-level variables, check not null and not empty string
-	return fmt.Sprintf("(%s != null && %s != \"\")", celField, celField), nil
+	// For nested fields use optional-safe selection (a.?b.?c) to avoid errors when
+	// intermediate selectors are null or missing.
+	if strings.Contains(celField, ".") {
+		parts := strings.Split(celField, ".")
+		if len(parts) > 1 {
+			optionalPath := parts[0]
+			for _, p := range parts[1:] {
+				optionalPath += ".?" + p
+			}
+			// Presence == "final optional is non-null"
+			return fmt.Sprintf("%s != null", optionalPath), nil
+		}
+	}
+	// For top-level variables, keep the existing "not null and not empty string" check.
+	return fmt.Sprintf("(%s != null && %s != \"\")", celField, celField), nil

If you apply this, the exists-related expectations in cel_evaluator_test.go should be updated to match the new optional-safe expressions (e.g. metadata.?name != null, cluster.?status.?phase != null).

Confirm that in cel-go with `cel.OptionalTypes()` enabled, the optional selection syntax `a.?b.?c != null` is the recommended way to safely test nested field presence without panicking on missing intermediates, compared to using `has(a.b.c)`.
🧹 Nitpick comments (9)
configs/broker-configmap-pubsub-template.yaml (1)

26-28: Optional: Consider a note clarifying the relationship between BROKER_TOPIC and BROKER_GOOGLEPUBSUB_TOPIC.

The template introduces BROKER_TOPIC (adapter-specific) while the broker library config references topic in the broker.yaml section, and the README documents BROKER_GOOGLEPUBSUB_TOPIC as a broker library environment variable override. Developers may wonder why multiple topic-related variables exist. A brief comment explaining their distinct purposes (adapter-side routing vs. broker library configuration) could prevent confusion.

Also applies to: 51-51

test/integration/k8s_client/helper_testcontainers_k3s.go (1)

96-107: Good defensive cleanup, but consider potential double-termination.

The immediate t.Cleanup registration prevents container leaks if subsequent assertions fail. However, note that:

  1. The inner nil check on line 100 is redundant since line 98 already guards entry.
  2. Tests using this setup typically call e.Cleanup(t) explicitly (lines 159-171), which will attempt Terminate() again. This is safe (testcontainers handles it gracefully) but may produce warning logs.

Consider setting e.Container = nil in the explicit Cleanup method to avoid the double-termination attempt:

 func (e *TestEnvTestcontainers) Cleanup(t *testing.T) {
 	t.Helper()
 	if e.Container != nil {
 		e.Log.Infof("Stopping K3s container...")
 		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 		defer cancel()
 		
 		err := e.Container.Terminate(ctx)
+		e.Container = nil // Prevent double-termination from t.Cleanup
 		if err != nil {
 			t.Logf("Warning: Failed to stop K3s container: %v", err)
 		}
 	}
 }
cmd/adapter/main.go (1)

138-163: Remove temporary unused variable suppression before merge.

The _ = adapterConfig and _ = apiClient assignments (lines 158-159) are acceptable placeholders during development, but should be removed once the TODO items are implemented. Consider adding a tracking issue for the implementation.

Would you like me to open an issue to track the event processing implementation?

test/integration/config-loader/loader_template_test.go (1)

16-19: Consider documenting the fragility of relative path navigation.

The runtime.Caller(0) approach works but is sensitive to file relocation. The comment on lines 13-15 helps, but this pattern can break if the file is moved. Consider adding a note about this or using an environment variable fallback for the project root.

internal/config_loader/accessors.go (1)

24-79: Template variable validation currently mis-flags nested resources.<name> usages

GetDefinedVariables registers resource aliases as "resources."+r.Name, e.g. resources.clusterNamespace. However isVariableDefined only treats either the full variable (varName) or its first path segment (parts[0]) as valid.

For a template like:

{{ .resources.clusterNamespace.metadata.namespace }}

the regex extracts varName = "resources.clusterNamespace.metadata.namespace", which:

  • Does not match the exact key resources.clusterNamespace, and
  • Has root = "resources", which is not in definedParams.

As a result, this valid usage is reported as “undefined template variable”, even though the alias resources.clusterNamespace is defined and intended to be dereferenced.

A simple way to make resource aliases behave like proper roots for nested paths is to recognize the resources.<alias> prefix explicitly:

func (v *Validator) isVariableDefined(varName string) bool {
	// Check exact match
	if v.definedParams[varName] {
		return true
	}

-	// Check if the root variable is defined (for nested paths like clusterDetails.status.phase)
-	parts := strings.Split(varName, ".")
-	if len(parts) > 0 {
-		root := parts[0]
-		if v.definedParams[root] {
-			return true
-		}
-	}
+	// Check if the root variable is defined (for nested paths).
+	parts := strings.Split(varName, ".")
+	if len(parts) > 0 {
+		root := parts[0]
+
+		// Handle simple root variables (e.g. "metadata", "clusterId").
+		if v.definedParams[root] {
+			return true
+		}
+
+		// Special handling for resource aliases: treat "resources.<name>" as a root.
+		if root == "resources" && len(parts) > 1 {
+			alias := root + "." + parts[1]
+			if v.definedParams[alias] {
+				return true
+			}
+		}
+	}

	return false
}

This keeps existing behavior for simple variables while allowing nested access via resources.<alias>.* to validate correctly.

Also applies to: 231-248

internal/criteria/evaluator.go (4)

85-100: Simplify redundant error handling branches.

The conditional at lines 94-97 is redundant since both branches return false. The explicit IsFieldNotFound check doesn't change the outcome.

 func (e *Evaluator) EvaluateConditionSafe(field string, operator Operator, value interface{}) bool {
 	result, err := e.EvaluateConditionWithResult(field, operator, value)
 	if err != nil {
-		// For "exists" operator, field not found means not exists
-		if operator == OperatorExists {
-			return false
-		}
-		// For other operators, check if it's a field not found error
-		if IsFieldNotFound(err) {
-			return false
-		}
+		// Any error (including field not found) returns false for safe evaluation
 		return false
 	}
 	return result.Matched
 }

Alternatively, if future differentiation is planned between error types, consider adding a comment explaining the intent.


215-240: Consider caching CELEvaluator for repeated evaluations.

Each CEL method creates a new CELEvaluator, which internally creates a new CEL environment. This is safe but may be suboptimal for scenarios with repeated evaluations on the same context.

If batch CEL evaluations are expected, consider lazily caching the CELEvaluator:

type Evaluator struct {
	context     *EvaluationContext
	celEval     *CELEvaluator
	celEvalOnce sync.Once
	celEvalErr  error
}

func (e *Evaluator) getCELEvaluator() (*CELEvaluator, error) {
	e.celEvalOnce.Do(func() {
		e.celEval, e.celEvalErr = NewCELEvaluator(e.context)
	})
	return e.celEval, e.celEvalErr
}

This is optional since the current approach is simpler and avoids synchronization complexity.


270-275: Consider using the Operator type for consistency.

ConditionDef.Operator uses string while EvaluationResult.Operator uses the Operator type. While the string-based approach offers flexibility for deserialization, using the typed Operator would provide compile-time safety.

If YAML/JSON unmarshaling is a concern, consider adding custom unmarshaling logic to maintain type safety.


310-334: evaluateContains does not support map types.

The contains operator handles strings and slices/arrays but returns an error for maps. Map key containment is a common use case (e.g., checking if a label key exists).

Consider adding map key support:

 // For slices/arrays
 value := reflect.ValueOf(fieldValue)
+if value.Kind() == reflect.Map {
+	// Check if needle is a key in the map
+	needleVal := reflect.ValueOf(needle)
+	if needleVal.Type().AssignableTo(value.Type().Key()) {
+		return value.MapIndex(needleVal).IsValid(), nil
+	}
+	return false, fmt.Errorf("needle type %T not compatible with map key type", needle)
+}
 if value.Kind() == reflect.Slice || value.Kind() == reflect.Array {

This may not be needed if map containment is handled elsewhere or not a requirement.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6174f0e and 4f52202.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (37)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
  • pkg/errors/api_error.go
  • OWNERS
  • internal/config_loader/validator_test.go
  • internal/criteria/evaluator_test.go
  • test/integration/config-loader/config_criteria_integration_test.go
  • configs/adapter-config-template.yaml
  • test/integration/k8s_client/helper_envtest_prebuilt.go
  • internal/criteria/README.md
  • test/testdata/templates/deployment.yaml
  • configs/templates/deployment.yaml
  • configs/templates/job.yaml
  • internal/hyperfleet_api/README.md
  • internal/hyperfleet_api/client_test.go
  • internal/config_loader/loader.go
  • internal/config_loader/README.md
🧰 Additional context used
🧬 Code graph analysis (11)
internal/config_loader/validator.go (4)
pkg/errors/error.go (1)
  • Errors (77-95)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (84-92)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/loader.go (1)
  • Parse (114-130)
cmd/adapter/main.go (5)
internal/config_loader/loader.go (3)
  • EnvConfigPath (22-22)
  • Load (88-111)
  • WithAdapterVersion (55-59)
internal/config_loader/types.go (1)
  • HyperfleetAPIConfig (35-39)
internal/hyperfleet_api/client.go (7)
  • BaseURLFromEnv (123-125)
  • EnvBaseURL (21-21)
  • ClientOption (37-37)
  • WithTimeout (66-70)
  • WithRetryAttempts (73-77)
  • WithRetryBackoff (80-84)
  • NewClient (101-120)
internal/hyperfleet_api/types.go (2)
  • Client (214-232)
  • BackoffStrategy (13-13)
internal/k8s_client/client.go (2)
  • Client (20-23)
  • NewClient (58-101)
test/integration/broker_consumer/adapter_integration_test.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (66-70)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (2)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (34-51)
  • ConditionToCEL (205-241)
  • ConditionsToCEL (313-328)
internal/criteria/evaluator.go (2)
  • ConditionDef (271-275)
  • NewEvaluator (41-48)
internal/hyperfleet_api/client.go (2)
internal/hyperfleet_api/types.go (9)
  • Client (214-232)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
pkg/errors/api_error.go (1)
  • NewAPIError (121-132)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (114-121)
  • Precondition (74-81)
  • PostAction (143-147)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (84-92)
  • Resource (114-121)
  • SelectorConfig (131-134)
  • Parameter (47-63)
  • FetchExternalResource (66-71)
  • Precondition (74-81)
internal/config_loader/loader.go (4)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
  • ValidHTTPMethodsList (39-39)
internal/criteria/cel_evaluator.go (2)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/evaluator.go (1)
  • ConditionDef (271-275)
internal/config_loader/loader_test.go (3)
internal/config_loader/loader.go (7)
  • Load (88-111)
  • Parse (114-130)
  • LoadWithVersion (134-136)
  • ParseWithVersion (140-142)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (62-66)
internal/config_loader/types.go (10)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
  • AdapterConfig (4-9)
  • AdapterConfigSpec (19-27)
  • AdapterInfo (30-32)
  • Parameter (47-63)
  • Resource (114-121)
  • PostConfig (137-140)
  • DiscoveryConfig (124-128)
  • SelectorConfig (131-134)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (446-458)
  • IsSupportedAPIVersion (436-443)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • OperatorExists (26-26)
  • EvaluationError (97-101)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
internal/criteria/cel_evaluator.go (4)
  • CELResult (22-31)
  • NewCELEvaluator (34-51)
  • ConditionToCEL (205-241)
  • ConditionsToCEL (313-328)
🔇 Additional comments (57)
configs/README.md (1)

25-25: ✓ Naming convention updates are consistent and well-documented.

The variable renames (SUBSCRIPTION_ID → BROKER_SUBSCRIPTION_ID) are applied consistently across quick-start examples, environment variable tables, and supporting notes. The added clarification at line 67 helpfully distinguishes between adapter-specific and broker library variables.

Also applies to: 62-62, 67-67

configs/broker-configmap-pubsub-template.yaml (1)

24-24: ✓ ConfigMap template keys and example deployment references are consistent.

Variable renames properly flow through: ConfigMap data keys (lines 24, 28) are correctly referenced in the example deployment (lines 93, 97). Comments at lines 26–27 clearly explain the adapter-specific scope of these variables, distinguishing them from broker library configuration in the broker.yaml section.

Also applies to: 28-28, 93-93, 97-97

go.mod (2)

15-15: Based on the web search results, I now understand the context better. Let me provide you with the rewritten review comment:


Clarify intentional use of dual YAML module paths in dependency graph.

The go.mod includes both gopkg.in/yaml.v3 v3.0.1 (line 15, direct) and go.yaml.in/yaml/v3 v3.0.4 (line 118, indirect). These are legitimate but distinct module paths: gopkg.in/yaml.v3 is the legacy import path, while go.yaml.in/yaml/v3 is the newer official path under the YAML organization. Both paths refer to the same upstream project but represent a migration to a new module host.

The presence of both versions is likely due to a transitive dependency that declares go.yaml.in/yaml/v3 as its module path. While Go modules permit multiple module paths for the same logical package, this setup may indicate inconsistent dependency management—some dependencies using the older path, others using the newer one.

Verify:

  • Which transitive dependency introduces go.yaml.in/yaml/v3 v3.0.4
  • Whether having both versions loaded is acceptable or if dependencies should be unified on a single module path

23-23: Dependency compatibility verified; CEL and ANTLR versions are compatible.

Web search confirms that cel.dev/expr and antlr4-go/antlr/v4 are mutually compatible by design. Specifically, cel.dev/expr v0.24.0 explicitly depends on github.com/antlr4-go/antlr/v4 v4.13.0, and CEL's official documentation states that parsing uses ANTLR lexer/parser grammars. The dependency additions align with CEL-based expression evaluation and parsing requirements. No version conflicts identified.

test/integration/broker_consumer/adapter_integration_test.go (2)

91-91: LGTM: Timeout increase is reasonable.

Doubling the timeout from 30s to 60s accommodates slower CI environments and the new polling mechanism.


126-152: Good improvement: Polling replaces arbitrary sleep.

The publishAndWaitForMessage helper properly handles the race condition where the subscriber may not be ready immediately after Subscribe() returns. The implementation is clean with proper resource cleanup via defer ticker.Stop().

One minor consideration: the 500ms polling interval means messages may accumulate if the subscriber is slow to initialize. This is acceptable for smoke tests but worth noting.

configs/templates/cluster-status-payload.yaml (1)

1-16: LGTM: Well-structured template with appropriate defaults.

The template provides sensible defaults for condition fields, preventing missing-field issues when optional values aren't supplied. The ISO8601 timestamp format on line 6 is correct.

test/integration/testutil/container.go (3)

156-169: Good defensive cleanup for failed container starts.

Properly terminates containers that were created but failed to start fully (e.g., wait strategy timeout), preventing leaks during retry attempts. The fallback to forceCleanupContainer when Terminate fails is a nice touch.

Minor: Line 160 shadows the outer ctx variable from line 118, but this is intentional and clear in context.


178-204: LGTM: Enhanced cleanup with container ID tracking.

The explicit container ID capture and increased minimum cleanup timeout (60s) improve reliability. The fallback to force cleanup provides a safety net when graceful termination fails.


263-298: Useful utility for cleaning up leaked containers.

The CleanupLeakedContainers function provides a convenient way to clean up stray containers from previous failed test runs. The ancestor= filter correctly matches containers by image.

One consideration: if both docker and podman have containers matching the pattern, only the first runtime's containers will be cleaned (due to return on line 296). This is likely acceptable since most environments use only one runtime.

internal/config_loader/validator_schema.go (4)

16-29: Clean validator design with fail-fast semantics.

The SchemaValidator struct properly encapsulates validation state and provides a clear API with ValidateStructure(), ValidateFileReferences(), and LoadFileReferences().


31-51: Good use of fail-fast validation pattern.

The validator slice approach is clean and maintainable. Adding new validators only requires appending to the slice.


164-190: Thorough discovery validation.

The validation correctly enforces that discovery configuration is required for all resources (to find them on subsequent messages) and requires either byName or bySelectors to be configured.


464-498: Legacy wrappers create overhead but maintain compatibility.

Each legacy function creates a new SchemaValidator instance. This is acceptable for validation which typically runs once at startup, but could be optimized if called frequently.

cmd/adapter/main.go (3)

30-35: LGTM!

Clean command-line flag definition with helpful usage message that references the environment variable alternative.


62-91: LGTM - well-structured initialization flow.

The configuration loading and API client creation follows a clear pattern with proper error handling and informative logging. Good practice to verify the API base URL early before proceeding.


188-218: LGTM - robust API client factory with validation.

The createAPIClient function properly:

  • Parses timeout using the accessor method
  • Validates retry backoff strategy against known constants
  • Returns clear error messages for invalid configurations

One minor observation: if timeout == 0 after parsing (which could happen with an empty string), it silently uses the client's default. This behavior seems intentional based on the if timeout > 0 check.

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

21-106: Comprehensive integration test for the shipped template.

This test provides good coverage of the configuration structure, validating:

  • Basic metadata and API version
  • HyperFleet API settings
  • Parameters, preconditions, resources, and post actions

The assertions are well-structured and incrementally verify the configuration hierarchy.


108-121: LGTM!

Simple and focused test for loading a valid test configuration.


123-131: LGTM - safe helper implementation.

Returning a pointer to a slice element is safe here since the slice outlives the pointer usage within the test scope.

internal/criteria/evaluator_scenarios_test.go (5)

10-89: Well-designed real-world scenario test.

This test effectively mirrors the adapter config template structure, validating:

  • Nested cluster details from API responses
  • Field extraction and context population
  • Individual and combined precondition evaluation

Good coverage of the In, Exists, and combined EvaluateConditions paths.


91-151: LGTM - good resource status evaluation coverage.

Tests nested field path resolution (resources.clusterNamespace.status.phase) and numeric comparisons effectively.


153-187: LGTM!

Tests nested map access and the Contains operator for slice membership checks.


189-218: LGTM - important negative test case.

Testing that terminating clusters correctly fail preconditions is valuable for ensuring the adapter doesn't process clusters in invalid states.


220-299: LGTM - well-structured table-driven test.

Good coverage of boundary conditions:

  • Valid range
  • Below minimum
  • Above maximum
  • At minimum/maximum boundaries

Each subtest correctly creates an isolated context and evaluator for parallel safety.

internal/config_loader/loader_test.go (15)

12-71: LGTM - solid basic loading test.

Good practice using t.TempDir() for temporary file creation with automatic cleanup.


73-78: LGTM!

Concise test for invalid path error handling.


80-174: LGTM - comprehensive parsing validation.

Table-driven tests cover the essential validation cases for required fields.


176-261: LGTM!

Good coverage of parameter validation including the requirement for source, build, buildRef, or fetchExternalResource.


263-395: LGTM - thorough precondition validation testing.

Tests cover name requirements, API call validation, method validation, and the requirement for apiCall/expression/conditions.


397-489: LGTM!

Resource validation tests cover name and manifest requirements.


491-603: LGTM - accessor method tests are well-structured.

Tests for GetRequiredParams, GetResourceByName, and GetPreconditionByName verify both found and not-found cases.


605-617: LGTM!

Simple and effective timeout parsing test.


619-652: LGTM - schema validation tests.

Good coverage of unsupported API versions and invalid kind values with proper error message assertions.


654-759: LGTM - version validation tests are thorough.

Tests cover:

  • Matching versions
  • Mismatched versions with proper error messages
  • Empty expected version (skip validation)
  • Supported API version checks

761-890: LGTM - comprehensive file reference validation.

Good coverage of:

  • Valid and invalid buildRef paths
  • Directory vs file detection
  • manifest.ref validation
  • post.params buildRef
  • No file references case

892-983: LGTM!

Tests loading with file references including both success and failure cases for non-existent files.


985-1066: LGTM - verifies content loading behavior.

Good test that validates:

  • manifest.ref content replaces the ref
  • buildRef content is loaded into BuildRefContent
  • Original BuildRef path is preserved

1068-1263: LGTM - thorough resource discovery validation.

Comprehensive table-driven tests covering:

  • Valid bySelectors and byName configurations
  • Missing discovery, namespace, and selector requirements
  • Both manifest.ref and inline manifest cases
  • manifest.refs array support

1265-1334: LGTM - manifest refs array loading test.

Validates that multiple manifest references are correctly loaded and populated in ManifestItems.

test/testdata/adapter_config_valid.yaml (5)

1-23: LGTM!

Clean configuration header with appropriate metadata and HyperFleet API settings including retry configuration.


24-51: LGTM - well-structured parameter definitions.

Parameters include both environment-sourced (hyperfleetApiBaseUrl, hyperfleetApiVersion, hyperfleetApiToken) and event-sourced (clusterId, resourceId) values with appropriate type annotations.


52-89: LGTM - comprehensive precondition configuration.

The clusterStatus precondition demonstrates a complete flow:

  • API call with templated URL, timeout, and retry settings
  • Field extraction with meaningful aliases
  • Structured conditions using valid operators (in, exists)

The validationCheck precondition shows a valid CEL expression alternative.


135-202: LGTM - comprehensive post-processing configuration.

The post section demonstrates:

  • Complex status payload builder with CEL expressions
  • Proper condition structure (applied, available, health)
  • Post action with templated API call, headers, and conditional execution

This provides excellent test coverage for the post-processing evaluation paths.


90-134: Based on the failed attempts to access the repository, I cannot verify the actual existence of the templates/deployment.yaml file or how tests handle it. This is a situation where manual verification is necessary.

Verify that the external template file templates/deployment.yaml exists relative to the test configuration, or that the test framework handles the reference appropriately.

The externalTemplate resource in test/testdata/adapter_config_valid.yaml (line 128) references ref: "templates/deployment.yaml". Without access to the repository, I cannot confirm whether this file exists or how the test framework resolves relative paths in the manifest reference. A developer should verify: (1) the file exists at the expected location relative to test execution context, or (2) the test framework gracefully handles missing external template references.

internal/criteria/types.go (1)

7-39: Core criteria types and error model look solid

The operator definitions, validation helpers, evaluation context, and EvaluationError struct are cohesive and straightforward, with no apparent correctness or API design issues. This is a good foundation for the rest of the criteria system.

Also applies to: 60-112

internal/config_loader/validator.go (1)

18-95: Validation pipeline is cohesive and matches the config semantics

The validator cleanly aggregates operator checks, template-variable resolution, CEL parse/type validation, and basic Kubernetes manifest validation into a single entry point, with good error aggregation via ValidationErrors. The use of CEL optional types and dynamic env construction is appropriate for your config model.

Also applies to: 111-145, 150-270, 276-350, 405-465

internal/hyperfleet_api/types.go (1)

12-32: HTTP client types and options are well-structured

The BackoffStrategy, ClientConfig defaults, request option helpers, response helpers, and Client interface are consistent and give a clear, extensible surface for the HTTP client. They line up cleanly with the implementation in client.go.

Also applies to: 37-63, 69-150, 155-208, 213-232

internal/config_loader/types.go (1)

3-39: Adapter config schema is cohesive and matches loader/validator usage

The configuration types clearly model the YAML schema (adapter info, Hyperfleet API, Kubernetes, params, preconditions, resources, post actions) and align with how accessors and the validator consume them. The added fields for Build/BuildRef, discovery, and post-processing look well thought out.

Also applies to: 46-121, 123-159

internal/criteria/evaluator.go (8)

9-33: LGTM!

The result types are well-documented with clear field semantics. Using -1 as a sentinel for FailedCondition is a reasonable convention.


35-48: LGTM!

Defensive nil handling in the constructor is a good pattern, ensuring the evaluator is always usable.


50-74: LGTM!

The field access API provides appropriate variants for different use cases: strict (GetField), defaulting (GetFieldOrDefault), and null-safe (GetFieldSafe, HasField).


102-155: LGTM!

The operator switch handles all defined operators with appropriate error propagation. Negation logic for NotEquals and NotIn is correctly applied after the base evaluation.


166-191: LGTM!

Evaluating all conditions (even after a failure) is a good design choice as it enables complete introspection of results for debugging and logging purposes.


420-423: Unfortunately, I'm unable to clone the repository to verify the claims in the review comment. The shell execution encountered an unrecoverable error during repository cloning.

Given this limitation, I cannot definitively verify whether getNestedField is truly unused or how EvaluationContext.GetNestedField is implemented. Without access to the codebase, I cannot:

  • Search for all calls to getNestedField across files
  • Examine the EvaluationContext.GetNestedField implementation to see if it delegates to the standalone function
  • Confirm the function's actual usage patterns

Rewritten Review Comment:

Verify if getNestedField function is actually unused or if it's called by EvaluationContext.GetNestedField.

The review comment claims this standalone function is unused because Evaluator uses e.context.GetNestedField() instead. However, EvaluationContext.GetNestedField may internally delegate to this function. Confirm whether this function is truly dead code or if it serves as an internal utility for the context method.


499-513: Potential panic when accessing unexported struct fields.

f.Interface() on line 512 will panic if the field is unexported (embedded structs can expose unexported fields that are valid but not interface-able). Consider adding a CanInterface() check.

 	if !f.IsValid() {
 		return nil, &FieldNotFoundError{Path: path, Field: field,
 			Message: fmt.Sprintf("field '%s' not found in struct", path)}
 	}

+	if !f.CanInterface() {
+		return nil, &FieldNotFoundError{Path: path, Field: field,
+			Message: fmt.Sprintf("field '%s' is unexported", path)}
+	}
+
 	return f.Interface(), nil

Likely an incorrect or invalid review comment.


388-418: Based on my investigation:

Unable to verify the concern directly — the repository cannot be accessed to confirm whether json.Decoder.UseNumber() is actually used in the hyperfleet-adapter codebase.

Key finding from web research:

  • json.Number is only relevant when json.Decoder.UseNumber() is explicitly called (it's not the default behavior)
  • The review comment assumes this is enabled but provides no evidence from the code

Status:
The concern is theoretically sound — if UseNumber() is enabled, then json.Number values would indeed cause toFloat64 to fail. However, without access to the actual codebase, I cannot confirm whether this scenario actually occurs.


toFloat64 does not handle json.Number type.

When unmarshaling JSON into map[string]interface{} with json.Decoder.UseNumber(), numeric values are represented as json.Number. The current implementation would return an error for such values.

Verify if UseNumber() is used in the config loader or event processing. If confirmed, add handling:

+case json.Number:
+	return v.Float64()
 default:
 	return 0, fmt.Errorf("cannot convert %T to float64", value)

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

♻️ Duplicate comments (3)
internal/hyperfleet_api/client.go (1)

144-159: Note: Zero/negative retry attempts issue deferred.

As noted in the previous review, if retryAttempts <= 0, the loop at line 159 never executes and no HTTP request is made. The author acknowledged this but deferred the fix. Consider tracking this as a follow-up task.

internal/criteria/cel_evaluator.go (2)

230-237: The exists operator for nested fields remains unsafe despite the comment.

The comment claims cel.OptionalTypes() makes has() safely handle null intermediates, but this is incorrect per CEL specification. has(a.b.c) will error if a.b is null — it does not return false.

For nested paths like cluster.status.phase, the current output has(cluster.status.phase) will fail at runtime if any intermediate is null.

Consider one of these fixes:

  1. Chain null checks: has(cluster) && has(cluster.status) && has(cluster.status.phase)
  2. Use optional-safe selection (if supported): cluster.?status.?phase != null
 	case "exists":
-		// CEL has() macro checks if a field exists on an object
-		// With cel.OptionalTypes() enabled, has() safely handles null intermediates
 		if strings.Contains(celField, ".") {
-			return fmt.Sprintf("has(%s)", celField), nil
+			// Build chained has() checks for null-safe nested field existence
+			parts := strings.Split(celField, ".")
+			checks := make([]string, len(parts))
+			for i := range parts {
+				prefix := strings.Join(parts[:i+1], ".")
+				checks[i] = fmt.Sprintf("has(%s)", prefix)
+			}
+			return strings.Join(checks, " && "), nil
 		}
 		// For top-level variables, check not null and not empty string
 		return fmt.Sprintf("(%s != null && %s != \"\")", celField, celField), nil

249-253: String escaping remains incomplete for special characters.

The past review flagged this issue and it was marked as addressed, but the current code still only escapes double quotes. Backslashes, newlines, and other special characters need escaping to produce valid CEL string literals.

Consider using strconv.Quote which handles all escaping properly:

 	case string:
-		// Escape quotes in string
-		escaped := strings.ReplaceAll(v, `"`, `\"`)
-		return fmt.Sprintf(`"%s"`, escaped), nil
+		return strconv.Quote(v), nil
🧹 Nitpick comments (5)
test/integration/k8s_client/helper_envtest_prebuilt.go (1)

157-160: Consider replacing fixed sleep with readiness polling.

The 5-second sleep after the wait strategy could lead to flaky tests if the API server needs more (or less) time. Consider polling a health or readiness endpoint instead, though this may be acceptable for integration test reliability.

internal/criteria/evaluator_test.go (2)

325-370: Consider adding a non-numeric error case for evaluateLessThan.

TestEvaluateGreaterThan includes a test for non-numeric field (line 305-309), but TestEvaluateLessThan is missing an equivalent test case. For consistency and complete coverage:

 		{
 			name:      "equal values",
 			field:     10,
 			threshold: 10,
 			want:      false,
 		},
+		{
+			name:      "non-numeric field",
+			field:     "test",
+			threshold: 10,
+			wantError: true,
+		},
 	}

372-426: Consider adding a test case for zero value.

The exists tests cover various types but don't test numeric zero. Depending on the intended semantics, zero might need explicit testing:

 		{
 			name:  "number",
 			field: 42,
 			want:  true,
 		},
+		{
+			name:  "zero value",
+			field: 0,
+			want:  true, // or false, depending on intended semantics
+		},
internal/criteria/evaluator_scenarios_test.go (1)

131-150: Shared context mutation in subtest.

Lines 132-133 mutate the shared ctx within the subtest. While this works correctly because subtests run sequentially without t.Parallel(), it's a subtle coupling. Consider creating an isolated context per subtest for clarity and future-proofing:

 	t.Run("replicas equal ready replicas", func(t *testing.T) {
-		ctx.Set("replicas", 3)
-		ctx.Set("readyReplicas", 3)
-		
-		result, err := evaluator.EvaluateCondition(
+		// Create isolated context for this subtest
+		localCtx := criteria.NewEvaluationContext()
+		localCtx.Set("replicas", 3)
+		localCtx.Set("readyReplicas", 3)
+		localEvaluator := criteria.NewEvaluator(localCtx)
+
+		result, err := localEvaluator.EvaluateCondition(

This matches the pattern used in TestNodeCountValidation (line 329-335).

internal/criteria/types.go (1)

89-94: Consider adding nil check in Merge to prevent panic.

If other is nil, iterating over other.Data will panic. Consider adding a nil guard.

 func (c *EvaluationContext) Merge(other *EvaluationContext) {
+	if other == nil {
+		return
+	}
 	for k, v := range other.Data {
 		c.Data[k] = v
 	}
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f52202 and da704cf.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (37)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • internal/criteria/README.md
  • internal/config_loader/README.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • internal/hyperfleet_api/README.md
  • test/integration/testutil/container.go
  • test/integration/k8s_client/helper_testcontainers_k3s.go
  • configs/templates/job.yaml
  • configs/templates/deployment.yaml
  • pkg/errors/api_error.go
  • test/integration/config-loader/loader_template_test.go
  • test/testdata/templates/deployment.yaml
  • configs/README.md
🧰 Additional context used
🧬 Code graph analysis (11)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (280-284)
internal/hyperfleet_api/client.go (2)
internal/hyperfleet_api/types.go (10)
  • Client (214-232)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (54-63)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
pkg/errors/api_error.go (1)
  • NewAPIError (121-132)
internal/config_loader/loader_test.go (3)
internal/config_loader/loader.go (7)
  • Load (88-111)
  • Parse (114-130)
  • LoadWithVersion (134-136)
  • ParseWithVersion (140-142)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (62-66)
internal/config_loader/types.go (5)
  • Metadata (12-16)
  • AdapterConfig (4-9)
  • AdapterConfigSpec (19-27)
  • Parameter (47-63)
  • Resource (114-121)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (474-486)
  • IsSupportedAPIVersion (464-471)
internal/hyperfleet_api/client_test.go (3)
internal/hyperfleet_api/client.go (2)
  • NewClient (100-118)
  • ClientOption (36-36)
internal/hyperfleet_api/types.go (5)
  • ClientConfig (38-51)
  • Request (70-85)
  • WithHeader (103-110)
  • DefaultClientConfig (54-63)
  • Response (156-169)
pkg/errors/api_error.go (2)
  • NewAPIError (121-132)
  • IsAPIError (142-148)
internal/criteria/evaluator_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
  • EvaluationError (97-101)
internal/criteria/evaluator.go (5)
  • NewEvaluator (48-55)
  • ConditionDef (280-284)
  • EvaluationResult (12-23)
  • IsFieldNotFound (568-571)
  • FieldNotFoundError (556-560)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (34-51)
  • ConditionToCEL (205-241)
  • ConditionsToCEL (313-328)
internal/criteria/evaluator.go (2)
  • ConditionDef (280-284)
  • NewEvaluator (48-55)
internal/config_loader/validator.go (2)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (84-92)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (114-121)
  • Precondition (74-81)
  • PostAction (143-147)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (474-486)
internal/config_loader/validator.go (1)
  • Validate (486-488)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (4)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (97-101)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (34-51)
  • CELResult (22-31)
  • ConditionToCEL (205-241)
  • ConditionsToCEL (313-328)
🔇 Additional comments (127)
test/integration/broker_consumer/adapter_integration_test.go (3)

91-91: LGTM - Appropriate timeout for integration test.

Doubling the timeout to 60 seconds is reasonable for integration tests with containerized dependencies, especially given the setup overhead mentioned in the PR comments (image downloads, envtest initialization).


118-124: LGTM - Robust polling approach replaces fixed sleep.

Replacing the fixed sleep with active polling eliminates race conditions where the subscriber may not be fully ready immediately after Subscribe() returns. The approach is more deterministic and fails faster on genuine issues while accommodating timing variability.


126-152: LGTM - Well-structured polling helper with proper resource management.

The helper function correctly implements ticker-based retry logic with context-driven termination. The immediate first publish (line 137) optimizes for the common case where the subscriber is ready, while the 500ms ticker handles slower startup scenarios.

Resource management is correct: ticker cleanup via defer, no goroutine leaks, and all code paths return a value.

OWNERS (1)

2-2: Good fix: quoting numeric identifier.

Quoting "86254860" ensures YAML parsers treat it as a string rather than an integer, which is correct for user identifiers.

configs/templates/cluster-status-payload.yaml (1)

1-15: Template structure looks good overall.

The template properly uses Go template syntax with sensible defaults for condition fields. The RFC3339 timestamp format on line 6 is appropriate for API payloads.

One consideration: observedGeneration (line 5) is rendered as a quoted string. Verify whether the consuming API expects this as an integer instead:

# Current (string):
observedGeneration: "{{ .eventGenerationId }}"

# If integer is expected, use unquoted or cast:
observedGeneration: {{ .eventGenerationId }}
configs/adapter-config-template.yaml (2)

123-128: New imageTag parameter looks good.

The parameter follows the established pattern with appropriate defaults and is correctly marked as optional.


384-388: Verify: Headers removed from reportClusterStatus API call.

The reportClusterStatus action no longer specifies Authorization or Content-Type headers. Ensure this is intentional—if headers aren't injected automatically by the HyperFleet API client, requests may fail due to:

  • Missing authentication (401/403 errors)
  • Missing Content-Type causing body parsing issues

If headers should be present, add them back:

        apiCall:
          method: "POST"
          url: "{{ .hyperfleetApiBaseUrl }}/api/{{ .hyperfleetApiVersion }}/clusters/{{ .clusterId }}/status"
          headers:
            Authorization: "Bearer {{ .hyperfleetApiToken }}"
            Content-Type: "application/json"
          body: "{{ .clusterStatusPayload }}"
test/integration/k8s_client/helper_envtest_prebuilt.go (2)

40-51: Good cleanup implementation.

The cleanup method now properly terminates the container with a reasonable 30-second timeout. Logging instead of failing is the correct approach since the container may already be stopped.


135-147: Solid setup-failure cleanup pattern.

The setupSuccess flag with deferred cleanup ensures containers are terminated even when setup assertions fail. This prevents resource leaks during test failures. The pattern also handles panics correctly since the deferred function will execute.

Also applies to: 186-187

configs/broker-configmap-pubsub-template.yaml (2)

93-97: Example deployment correctly updated.

The commented example now references the new BROKER_SUBSCRIPTION_ID key, keeping documentation in sync with the actual configuration.


24-28: Unable to verify breaking change impact due to repository access limitations.

The renamed keys (BROKER_SUBSCRIPTION_ID, BROKER_TOPIC) use a BROKER_ prefix that adds clarity and reduces namespace collision risk—this is good practice. However, the original review raises a valid concern about this being a breaking change for existing deployments.

Without access to the repository, I cannot confirm:

  • Whether old key names (SUBSCRIPTION_ID, TOPIC) still exist elsewhere in the codebase
  • Whether all consuming code has been updated to reference the new names
  • Whether deployed ConfigMaps reference the old or new keys

Before merging, verify that:

  • All application code consuming these environment variables has been updated
  • All existing ConfigMap deployments have been migrated to the new key names
  • Documentation reflects the new configuration keys
go.mod (2)

5-20: LGTM!

Direct dependencies are well-organized. The CEL library (v0.26.1) and YAML parser (v3.0.1) versions are appropriate, addressing previous review comments.


3-3: Go 1.25.0 is a valid, stable release.

Go 1.25.0 was released on August 12, 2025, and is the current stable version for the 1.25 series as of November 2025 (with point releases like 1.25.4 also available). Specifying go 1.25.0 is appropriate and intentional for this project.

internal/hyperfleet_api/types.go (5)

12-31: LGTM!

BackoffStrategy type and default constants are well-defined. The default values (10s timeout, 3 retries, exponential backoff, 1s-30s delay range) are sensible for production use.


37-63: LGTM!

ClientConfig struct is well-documented. DefaultClientConfig() correctly initializes the DefaultHeaders map to avoid nil map panics when adding headers.


69-149: LGTM!

The Request struct correctly uses pointer types for optional per-request overrides, enabling distinction between "not set" and explicit values. All RequestOption functions properly handle nil map initialization before adding headers.


155-207: LGTM!

Response helper methods correctly categorize HTTP status codes. IsRetryable appropriately includes timeout (408), rate-limiting (429), and gateway errors (502-504) alongside general server errors. BodyString safely handles nil body.


213-232: LGTM!

The Client interface is cleanly designed with context-first signatures enabling proper cancellation and timeout handling. The method signatures appropriately differentiate between read-only operations (Get, Delete) and write operations (Post, Put, Patch).

internal/config_loader/types.go (6)

1-27: LGTM!

The top-level config structures (AdapterConfig, Metadata, AdapterConfigSpec) follow Kubernetes resource conventions with proper YAML tags and clear field documentation.


29-44: LGTM!

HyperfleetAPIConfig uses string types for Timeout and RetryBackoff, which simplifies YAML parsing. The accessor methods (like ParseTimeout()) handle conversion to proper Go types.


46-71: LGTM!

Parameter struct appropriately uses interface{} for the flexible Build field and yaml:"-" for loader-populated fields (BuildRefContent). This cleanly separates YAML schema from runtime-populated data.


73-111: LGTM!

Precondition-related types (Precondition, APICall, Condition) provide flexible evaluation options. The Condition.Operator field uses the same string type as internal/criteria/types.go, ensuring consistency.


113-134: LGTM!

Resource uses interface{} for flexible manifest definitions and yaml:"-" for loader-populated arrays. DiscoveryConfig covers standard Kubernetes resource lookup patterns (namespace, name, label/field selectors).


136-159: LGTM!

Post-processing types (PostConfig, PostAction, WhenExpr) provide flexible conditional execution with support for both CEL expressions and structured conditions. ManifestRef cleanly wraps external manifest references.

cmd/adapter/main.go (5)

30-45: LGTM!

Command-line flag definition and parsing are correctly structured. The config path can be provided via flag or environment variable, offering flexibility.


62-71: LGTM!

Configuration loading with version injection and appropriate error handling. Logging the adapter name and namespace provides useful startup diagnostics.


73-91: LGTM!

API base URL validation ensures required configuration is present before proceeding. Client creation errors are properly handled and logged.


138-163: LGTM!

The event handler scaffold provides clear guidance on implementing event processing with proper context isolation. The placeholder assignments avoid unused variable warnings while the TODO comments outline the implementation steps.


188-218: LGTM!

createAPIClient correctly parses configuration and validates the backoff strategy. The zero-value checks allow falling back to client defaults when config values are not specified.

internal/hyperfleet_api/client_test.go (8)

18-93: LGTM!

Comprehensive client construction tests covering default creation and all configuration options. The table-driven test structure for TestNewClientWithOptions is clean and maintainable.


95-161: LGTM!

HTTP method tests properly verify request method, body transmission, and header handling. The closure-based capture of request details is a clean testing pattern.


194-297: LGTM!

Retry tests thoroughly cover success after retries, exhausted retries, and non-retryable 4xx errors. The use of atomic.AddInt32 ensures thread-safe attempt counting in concurrent test execution.


299-335: LGTM!

Timeout and context cancellation tests properly verify the client respects both client-level timeouts and context-based cancellation, essential for graceful shutdown behavior.


337-420: LGTM!

Response helper tests provide comprehensive status code coverage with a table-driven approach. Backoff calculation tests appropriately account for jitter with ±20% tolerance. Testing the internal calculateBackoff method directly ensures backoff logic is correct.


422-486: LGTM!

HTTP verb tests (Put, Patch, Delete) complete the client method coverage with proper method verification.


488-596: LGTM!

APIError tests thoroughly verify error string formatting, status helpers, and error extraction via errors.IsAPIError. The wrapped error test ensures proper error chain traversal.


598-642: LGTM!

TestAPIErrorInRetryExhausted provides excellent end-to-end validation that APIError correctly preserves method, status code, attempt count, and response body through the retry exhaustion flow.

internal/criteria/evaluator_test.go (13)

1-10: LGTM!

Clean import structure with appropriate test dependencies (stretchr/testify for assertions).


11-35: LGTM!

Good coverage of context lifecycle operations including Set, Get, and handling of nonexistent keys.


37-90: LGTM!

Comprehensive table-driven tests for nested field access with good coverage of success and error paths.


92-111: LGTM!

Merge behavior is correctly tested, including key preservation, overwriting, and addition.


113-170: LGTM!

Good coverage of equality comparisons including nil handling. The wantError field provides extensibility for future error cases.


172-217: LGTM!

Good coverage of the in operator including the error case when the value is not a list.


219-270: LGTM!

Thorough testing of the contains operator for both string substrings and array elements, including error handling for invalid types.


428-516: LGTM!

Comprehensive test coverage of EvaluateCondition across all operator types, including nested field access and error handling for nonexistent fields.


518-567: LGTM!

Good test coverage for combined condition evaluation including all-pass, partial-fail, and empty conditions scenarios.


569-665: LGTM!

Excellent coverage of getNestedField and toFloat64 utilities with comprehensive type handling.


667-740: LGTM!

Error handling tests and safe getter tests are well-structured with appropriate assertions.


742-873: LGTM!

EvaluateConditionWithResult and ExtractFields tests provide good coverage of result structures and field extraction scenarios.


891-991: LGTM!

Excellent null safety test coverage including deeply nested null paths, safe getters, and IsFieldNotFound error detection.

internal/config_loader/validator_test.go (7)

1-10: LGTM!

Clean imports with appropriate dependencies for testing config validation.


12-120: LGTM!

Good test coverage for operator validation including valid, invalid, and missing operator scenarios with appropriate error message assertions.


122-268: LGTM!

Comprehensive template variable validation tests covering defined variables, undefined variable detection in URLs and manifests, and nested variable access from stored responses.


270-366: LGTM!

Good CEL expression validation coverage including valid expressions, syntax errors, and the commonly used has() function for null-safety.


368-578: LGTM!

Thorough Kubernetes manifest validation tests covering all required fields (apiVersion, kind, metadata, metadata.name) and manifest references.


580-602: LGTM!

Good coverage of operator validation and error aggregation formatting.


604-678: LGTM!

Tests properly verify multi-error collection and builtin variable recognition for template processing.

internal/criteria/evaluator_scenarios_test.go (6)

1-8: LGTM!

Clean imports for scenario-based testing.


10-89: LGTM!

Excellent real-world scenario simulation that matches the adapter config template patterns, testing preconditions both individually and combined.


153-187: LGTM!

Good coverage of nested field access and the contains operator on slices for tracking created resources.


189-248: LGTM!

Excellent coverage of the Kubernetes labels containment pattern, testing both map[string]interface{} and typed map[string]string scenarios.


250-279: LGTM!

Good negative scenario testing for terminating cluster handling, verifying that terminated clusters correctly fail preconditions.


281-360: LGTM!

Excellent table-driven boundary testing for node count validation with isolated context per subtest. The pattern of using >=(min-1) and <=(max+1) correctly simulates inclusive bounds given the available greaterThan and lessThan operators.

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

1-14: LGTM!

Proper build tag for integration tests and clean imports.


16-96: LGTM!

Solid integration test that loads the actual config template and validates both per-condition and combined precondition evaluation with realistic runtime data.


98-152: LGTM!

Good CEL integration testing that validates condition-to-CEL conversion and subsequent CEL evaluation.


154-219: LGTM!

Comprehensive negative test cases validating that preconditions correctly fail for wrong phase, wrong provider, and missing required fields.


221-244: LGTM!

Good verification of resource discovery configuration including namespace, byName, and label selector validation.


246-338: LGTM!

Excellent post-processing simulation with realistic Kubernetes resource structures, testing nested field access, condition evaluation, and CEL expression evaluation.


340-395: LGTM!

Thorough null safety testing for missing and partially populated resources, validating safe access patterns and default value fallbacks.


417-427: LGTM!

The parameter source validation now correctly uses strings.HasPrefix to avoid potential panics, addressing the previous review feedback.

test/testdata/adapter_config_valid.yaml (4)

1-51: LGTM!

Well-structured test configuration with clear parameter definitions including required fields, types, and defaults.


52-89: LGTM!

Preconditions section demonstrates valid operator usage, field extraction, and CEL expression syntax.


90-134: LGTM!

Resources section demonstrates valid Kubernetes manifests with template variables, discovery configurations, and external template references.


135-202: LGTM!

Post-processing section demonstrates comprehensive CEL expression usage for condition building and conditional post-action execution.

internal/config_loader/loader_test.go (10)

1-10: LGTM!

Clean package declaration with appropriate test imports using testify for assertions.


12-71: LGTM!

Well-structured test covering the happy path for config loading with proper temp file cleanup.


80-174: LGTM!

Excellent table-driven tests covering all required field validations with clear test case names and expected error messages.


176-489: LGTM!

Thorough validation tests for parameters, preconditions, and resources with good coverage of error scenarios.


491-617: LGTM!

Good coverage of helper methods including edge cases like non-existent lookups and invalid timeout formats.


619-759: LGTM!

Comprehensive version validation tests covering supported/unsupported versions, version matching, and the skip-validation case when expected version is empty.


761-890: LGTM!

Thorough file reference validation tests covering valid refs, missing files, directory errors, and various reference locations (params, post.params, manifest.ref).


985-1066: LGTM!

Good verification that file references are properly loaded and content is correctly populated in BuildRefContent and Manifest fields.


1068-1263: LGTM!

Comprehensive resource discovery validation tests covering all required combinations: namespace, byName, bySelectors, and proper selector configuration.


1265-1334: LGTM!

Good test coverage for loading multiple manifest references (refs array) with proper verification of ManifestItems population.

internal/config_loader/validator_schema.go (7)

1-29: LGTM!

Clean package structure with well-organized SchemaValidator type encapsulating config validation.


31-69: LGTM!

Well-structured validation pipeline with fail-fast behavior and proper guards for baseDir-dependent operations.


75-246: LGTM!

Comprehensive structural validators with clear error messages including field paths for easy debugging.


252-324: LGTM!

Good error aggregation pattern for file reference validation, collecting all errors before returning for better user experience.


330-408: LGTM!

Clean implementation for loading file references with proper backward compatibility for single refs while supporting the new multi-ref array format.


410-440: Path traversal protection properly implemented.

The resolvePath function now correctly prevents path traversal attacks by:

  1. Converting paths to absolute form
  2. Using filepath.Rel to compute the relative path from base to target
  3. Rejecting any path where the relative starts with ..

This addresses the security concern from the previous review.


446-526: LGTM!

Good backward compatibility maintained through legacy wrapper functions while the core logic is properly encapsulated in the SchemaValidator type.

internal/criteria/types.go (2)

1-58: LGTM!

Well-designed operator type with comprehensive set of comparison operators and useful helper functions for validation and string conversion.


96-112: LGTM!

Well-designed error type with proper Unwrap() implementation for error chain support.

internal/hyperfleet_api/client.go (4)

1-33: LGTM - Data race issue addressed.

The previous review noted a data race risk from using *rand.Rand in the struct. This has been properly addressed by removing the rand field and using the package-level rand.Float64() which is concurrency-safe.


35-118: LGTM!

Clean functional options pattern implementation with sensible defaults.


217-313: LGTM!

Well-implemented request handling with proper context timeout, header management, and backoff calculation with jitter using the concurrency-safe rand.Float64().


319-380: LGTM!

Clean convenience methods following a consistent pattern and properly delegating to the Do method.

internal/config_loader/validator.go (7)

1-50: LGTM!

Well-designed error aggregation types with clear formatting and proper helper methods.


52-96: LGTM!

Good design pattern - the validator continues even if CEL environment initialization fails, collecting all possible errors for a comprehensive validation report.


98-144: LGTM!

Clean operator validation leveraging the criteria package's IsValidOperator function with proper error messages.


146-283: LGTM!

Comprehensive template variable validation with support for nested paths, resource aliases, and recursive map/array traversal.


285-412: LGTM!

Well-designed CEL validation that gracefully handles dynamic types - parse errors are reported while type check failures are logged as warnings since dynamic variables may cause false positives.


414-477: LGTM!

Proper Kubernetes manifest validation that correctly skips external template references while validating inline manifests for required fields.


479-488: LGTM!

Clean backward-compatible public API that wraps the new Validator type.

internal/config_loader/accessors.go (5)

1-79: LGTM! Well-designed accessors with consistent nil-safety.

The GetDefinedVariables method correctly aggregates variables from multiple sources (built-ins, params, precondition extracts, post params, and resources). The nil checks and empty string guards are appropriately placed.


81-145: LGTM! Correct pointer return pattern using index-based iteration.

The for i := range pattern with &c.Spec.X[i] correctly returns pointers to the actual slice elements, avoiding the common Go pitfall of returning addresses of loop variables.


175-182: LGTM!

Clean design where returning 0, nil for empty timeout allows callers to apply their own defaults.


188-246: LGTM! Robust manifest handling with YAML version compatibility.

The accessors correctly handle both ref (single) and refs (array) patterns, and normalizeToStringKeyMap properly handles the map[interface{}]interface{} type from yaml.v2, ensuring compatibility across YAML library versions.


252-274: LGTM!

The helper correctly handles the type differences between yaml.v2 (map[interface{}]interface{}) and yaml.v3 (map[string]interface{}). The fmt.Sprintf fallback for non-string keys is a reasonable defensive measure for edge cases.

internal/criteria/cel_evaluator_test.go (3)

12-113: LGTM! Comprehensive test coverage for CEL evaluator.

Good coverage of various expression types including string equality, numeric comparisons, boolean variables, compound expressions, and error cases like invalid syntax.


177-301: LGTM! Thorough operator coverage in condition conversion tests.

The tests cover all operators including the exists operator with different nesting depths (top-level, nested, deeply nested). Empty conditions returning "true" is correctly tested.


355-387: LGTM! Good coverage of formatCELValue edge cases.

The tests verify proper handling of nil values, string escaping, various numeric types, and correctly expect errors for unsupported types like channels, maps, and functions.

internal/criteria/cel_evaluator.go (3)

33-51: LGTM! Proper initialization with nil-safety.

The NewCELEvaluator correctly initializes a new context if nil is passed, and properly wraps the CEL environment creation error.


95-149: LGTM! Robust evaluation pipeline with graceful fallback.

The evaluation flow handles empty expressions, parse errors, and type-check failures appropriately. The fallback to the parsed AST when type-checking fails (common with dynamic types) is a pragmatic choice.


312-328: LGTM!

ConditionsToCEL correctly wraps each condition in parentheses before joining with &&, ensuring proper operator precedence.

internal/config_loader/loader.go (4)

16-27: LGTM! Well-defined constants and supported versions.

Clear separation of API version constants and supported versions list. This makes version management straightforward.


45-73: LGTM! Clean functional options pattern.

The options correctly modify the internal loaderConfig. Since Parse always initializes cfg := &loaderConfig{} before applying options, nil checks are unnecessary here.


84-130: LGTM! Well-designed loading API with sensible defaults.

The Load function automatically derives baseDir from the config file path but prepends it so user-provided WithBaseDir options can override. The validation pipeline is correctly invoked after unmarshaling.


151-197: LGTM! Well-structured validation pipeline.

The pipeline correctly:

  1. Runs core structural validators first
  2. Validates adapter version if specified
  3. Validates file references before loading them (correct order)
  4. Runs semantic validation last (can be skipped for performance)
internal/criteria/evaluator.go (7)

37-64: LGTM! Efficient lazy initialization of CEL evaluator.

The sync.Once pattern correctly ensures the CEL evaluator is created only once and reused for subsequent evaluations, avoiding the overhead of recreating the CEL environment.


66-90: LGTM! Consistent field access patterns with clear semantics.

The methods provide appropriate levels of error handling:

  • GetField: returns error for missing fields
  • GetFieldOrDefault: returns default on error/nil
  • GetFieldSafe: silently returns nil (null-safe)
  • HasField: boolean check for existence

110-163: LGTM! Comprehensive condition evaluation with operator support.

The EvaluateConditionWithResult method correctly handles all operators including the negation for notEquals and notIn. The result structure captures both the match status and the actual field value for debugging.


174-199: LGTM! Complete condition evaluation with full result capture.

The method correctly implements AND logic while still evaluating all conditions to provide complete diagnostic information. The FailedCondition index only captures the first failure, which is useful for debugging.


335-374: LGTM! Versatile contains implementation.

The function correctly handles multiple container types:

  • Strings: substring search
  • Maps: key existence check with type compatibility
  • Slices/Arrays: element membership

The special handling for interface{} keyed maps (common in YAML/JSON) is appropriate.


566-571: LGTM! Correctly uses errors.As for error unwrapping.

The implementation now properly detects FieldNotFoundError even when wrapped inside other error types like EvaluationError, addressing the past review feedback.


460-553: LGTM! Robust field traversal with comprehensive type support.

The recursive field access correctly handles:

  • map[string]interface{} (yaml.v3)
  • map[interface{}]interface{} (yaml.v2)
  • Structs with case-insensitive fallback

The case-insensitive matching only triggers when exact match fails, minimizing performance impact.

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

♻️ Duplicate comments (2)
internal/hyperfleet_api/client.go (1)

138-215: Known issue: Zero/negative retryAttempts prevents HTTP requests.

As noted in previous reviews, if retryAttempts is configured as 0 or negative, the loop at line 159 (for attempt := 1; attempt <= retryAttempts) never executes, resulting in no HTTP request being sent. While acknowledged by the author as acceptable for now, this remains a surprising behavior for API consumers.

internal/criteria/cel_evaluator.go (1)

307-314: Known issue: exists operator generates unsafe CEL for nested fields.

As noted in previous reviews, the exists operator at line 311 generates has(a.b.c) for nested fields, which will error at runtime if intermediate fields (like a.b) are null. The CEL has() macro requires evaluating the selector chain, so if a.b doesn't exist, accessing a.b.c throws an error before has() can check for presence.

For null-safe nested field checking, the expression should either:

  1. Chain parent checks: has(a) && has(a.b) && has(a.b.c)
  2. Use optional selection (if supported): a.?b.?c != null

The current implementation relies on EvaluateSafe() to capture these errors, which may be acceptable depending on the intended behavior. However, this should be documented or the exists operator should generate null-safe expressions.

🧹 Nitpick comments (10)
test/integration/broker_consumer/adapter_integration_test.go (1)

166-171: Minor: Inconsistent indentation.

Lines 166-171 have inconsistent indentation (leading tabs/spaces). This doesn't affect functionality but may cause issues if the project has strict formatting rules.

test/integration/k8s_client/helper_testcontainers_k3s.go (1)

96-106: Good defensive cleanup, but inner nil check is redundant.

Registering t.Cleanup immediately after container creation is a good practice to prevent leaks if subsequent assertions fail. However, the nil check on line 100 is redundant since you already check k3sContainer != nil on line 98.

 	if k3sContainer != nil {
 		t.Cleanup(func() {
-			if k3sContainer != nil {
-				ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-				defer cancel()
-				_ = k3sContainer.Terminate(ctx)
-			}
+			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+			defer cancel()
+			_ = k3sContainer.Terminate(ctx)
 		})
 	}

Note: This cleanup may run alongside the explicit Cleanup() method, but Terminate() is typically idempotent, so double-termination should be safe.

internal/config_loader/validator_schema.go (2)

75-246: Validation coverage is good; consider whether any additional required fields are desired.

The structural validators for API/kind, metadata, adapter spec, params, preconditions, resources (including discovery/selector semantics), post actions, and API calls are well‑scoped and produce clear error messages. If you intend metadata.namespace or minimum resource counts to be mandatory, you could extend these validators later, but the current behavior is coherent.


252-324: File‑reference handling and path‑traversal protection look correct, with minor error‑reporting tweaks possible.

  • Aggregating file‑reference errors across params/resources and returning a single bullet‑listed error is helpful for users.
  • validateFileExists correctly checks emptiness, existence, and that the target is a file.
  • resolvePath now uses filepath.Abs + filepath.Rel to ensure targets stay under baseDir, including for absolute refPath; this addresses the prior path‑traversal concern nicely.

If you want slightly clearer diagnostics, you could include the underlying filepath.Rel error instead of always reporting “escapes base directory”, but that’s optional.

Also applies to: 330-408, 410-440

configs/templates/job.yaml (1)

1-26: Job template looks consistent; verify .eventGenerationId is (or will be) a defined template variable.

Labels and naming match the cluster‑scoped patterns used elsewhere, and CLUSTER_ID wiring is straightforward. Just ensure that whatever config eventually references this template defines eventGenerationId (via params or built‑ins); otherwise the semantic/template validator will flag it as undefined when this job is wired in.

test/integration/config-loader/loader_template_test.go (1)

16-31: Integration tests give good coverage of the shipped template; WithBaseDir override is slightly redundant.

The tests nicely verify core fields, HyperFleet API settings, params, preconditions (including extracts/conditions), resources, and optional post config, which is valuable end‑to‑end coverage. Note that config_loader.Load already sets baseDir from configPath, so passing WithBaseDir(baseDir) with the same value is redundant here (though harmless if you want to exercise the option explicitly).

Also applies to: 39-106

internal/criteria/cel_evaluator_test.go (4)

140-218: EvaluateSafe semantics are well specified; consider adding one more negative case

These subtests clearly pin down EvaluateSafe behavior for success, missing fields, null dereference, has() behavior, empty expressions, and downstream conditional use of errors. As an optional enhancement, you could add a case asserting what Value should be on error (e.g., explicitly nil) to fully lock in the error-contract.


220-236: EvaluateBool happy paths covered; optional: add non-bool expression case

The tests verify true/false outcomes and error-free execution. As an optional hardening test, you could add an expression that does not evaluate to a boolean and assert that EvaluateBool fails appropriately (or document that it coerces).


238-255: EvaluateString tests look good; optional: add wrong-type assertion

String retrieval and concatenation are covered well. Optionally, you might add a case where the expression evaluates to a non-string (e.g., replicas) to assert that EvaluateString returns an error rather than silently coercing.


465-497: formatCELValue test matrix is solid; optional: extend slice coverage

The matrix covers nil, strings (with escaping), booleans, ints, floats, []string, custom int-like types, and several unsupported kinds (chan, map, func) with error expectations. Optionally, you could add a []int or mixed-type slice case to assert how non-string slices are handled (error vs. formatting) to fully anchor the behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da704cf and 182589c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (37)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
  • configs/README.md
  • test/testdata/templates/deployment.yaml
  • internal/config_loader/README.md
  • internal/criteria/evaluator_test.go
  • configs/templates/cluster-status-payload.yaml
  • configs/adapter-config-template.yaml
  • internal/hyperfleet_api/README.md
  • pkg/errors/api_error.go
  • OWNERS
  • internal/config_loader/validator_test.go
  • internal/hyperfleet_api/client_test.go
  • configs/templates/deployment.yaml
  • internal/criteria/evaluator.go
  • internal/config_loader/types.go
  • test/integration/config-loader/config_criteria_integration_test.go
🧰 Additional context used
🧬 Code graph analysis (8)
test/integration/config-loader/loader_template_test.go (2)
internal/config_loader/loader.go (2)
  • Load (88-111)
  • WithBaseDir (69-73)
internal/config_loader/types.go (3)
  • Metadata (12-16)
  • APICall (84-92)
  • ExtractField (101-104)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (84-92)
  • Resource (114-121)
  • SelectorConfig (131-134)
  • Parameter (47-63)
  • FetchExternalResource (66-71)
  • Precondition (74-81)
internal/config_loader/loader.go (4)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
  • ValidHTTPMethodsList (39-39)
test/integration/broker_consumer/adapter_integration_test.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (65-69)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (474-486)
internal/config_loader/validator.go (1)
  • Validate (486-488)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (114-121)
  • Precondition (74-81)
  • PostAction (143-147)
  • HyperfleetAPIConfig (35-39)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/criteria/evaluator.go (1)
  • ConditionDef (291-295)
internal/config_loader/loader_test.go (3)
internal/config_loader/loader.go (7)
  • Load (88-111)
  • Parse (114-130)
  • LoadWithVersion (134-136)
  • ParseWithVersion (140-142)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (62-66)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (474-486)
  • IsSupportedAPIVersion (464-471)
🔇 Additional comments (62)
test/integration/broker_consumer/adapter_integration_test.go (3)

91-91: LGTM on timeout increase.

Doubling the timeout from 30s to 60s is reasonable for integration tests with Pub/Sub emulators, which may have variable startup times.


118-124: Good improvement: active polling replaces fixed sleep.

Replacing the fixed sleep with a polling mechanism that publishes messages until one is received is more robust and avoids race conditions where the subscriber isn't ready immediately after Subscribe() returns.


126-152: Well-structured polling helper.

The publishAndWaitForMessage function:

  • Publishes immediately, then retries on a 500ms ticker
  • Uses context for timeout control
  • Properly cleans up the ticker via defer
  • Has clear return semantics
internal/criteria/README.md (1)

1-381: Comprehensive and well-structured documentation.

The README provides clear documentation covering operators, usage patterns, nested field access, context management, integration examples, error handling, and best practices. This will be valuable for developers working with the criteria package.

test/integration/testutil/container.go (4)

156-169: Good improvement: cleanup on partial container creation failure.

Terminating containers that were created but failed to start fully (e.g., due to wait strategy timeout) prevents resource leaks. The fallback to force cleanup via container ID is a sensible safety net.


178-204: Enhanced cleanup with container ID capture and extended timeout.

Capturing the container ID and name before registering cleanup, extending the timeout to at least 60s, and falling back to force cleanup on termination failure are all good improvements for reliability.


236-261: Force cleanup implementation addresses previous review feedback.

The current implementation now logs all errors without fragile string matching to detect "not found" errors. This addresses the concern from the previous review about string matching being fragile across Docker/Podman versions.


263-298: Useful utility for cleaning up leaked containers.

The CleanupLeakedContainers function provides a helpful way to clean up containers from previous failed test runs. The ancestor filter is appropriate for finding containers by image pattern.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

40-52: Good: Cleanup method now actually terminates the container.

Previously this may have been a no-op. Now it properly terminates the container with a 30-second timeout and logs any errors without failing the test (since the container might already be stopped).


135-147: Good pattern for setup failure cleanup.

The setupSuccess flag with deferred cleanup ensures the container is terminated if any assertions or setup steps fail after the container starts. This complements the t.Cleanup registered by StartContainer:

  • t.Cleanup runs at test end (success or failure)
  • This deferred cleanup runs immediately on setup failure within SetupTestEnvPrebuilt

Since Terminate is idempotent, duplicate calls are safe.

Also applies to: 186-187

go.mod (1)

7-15: Module dependency updates look consistent with new CEL/config loader and API client features.

Direct and indirect requirements here line up with the added criteria, YAML, and HTTP client functionality; nothing stands out as problematic from the module file itself.

Also applies to: 23-24, 36-37, 47-48, 104-105, 120-120

internal/config_loader/validator_schema.go (2)

16-51: SchemaValidator structure and fail‑fast structural validation look solid.

Centralizing core checks (API version/kind, metadata, spec, params, preconditions, resources, post actions) behind SchemaValidator and exposing ValidateStructure, ValidateFileReferences, and LoadFileReferences is clean and makes the loader pipeline easy to reason about.


446-486: Helper and legacy wrappers maintain backward compatibility cleanly.

The small helpers (hasParamSource, hasPreconditionLogic, IsSupportedAPIVersion, ValidateAdapterVersion) and the legacy wrapper functions that delegate to SchemaValidator keep the package API stable while routing all logic through the new validator, which is a nice compatibility story.

Also applies to: 492-526

test/testdata/adapter_config_valid.yaml (1)

16-52: Test config cleanly exercises the new schema and evaluation flow.

This YAML hits the key paths: required params from env/event, structured preconditions plus a CEL expression, resource manifests with discovery (byName/bySelectors), and a post block building clusterStatusPayload and reporting via an API call. It’s a good “happy path” fixture for both schema and semantic validation.

Also applies to: 52-89, 90-135, 135-202

configs/broker-configmap-pubsub-template.yaml (1)

24-28: ConfigMap key rename aligns with subscriber expectations.

Renaming to BROKER_SUBSCRIPTION_ID/BROKER_TOPIC and updating the example Deployment env/configMapKeyRef keeps the template consistent with the broker consumer and main command behavior.

Also applies to: 93-97

test/integration/config-loader/loader_template_test.go (1)

108-131: Valid config load smoke‑test and helper look correct.

TestLoadValidTestConfig provides a simple sanity check that the test config parses, and findExtractByAs correctly returns a pointer to the slice element (no range‑variable pitfalls).

cmd/adapter/main.go (2)

30-45: Config flag and adapter configuration loading are wired sensibly; confirm version matching behavior in your build pipeline.

Using --config with a fallback to ADAPTER_CONFIG_PATH and feeding that into config_loader.Load is straightforward. Passing WithAdapterVersion(version) is good for catching mismatches, but note that with the default version = "dev" this will fail against configs that declare a concrete version like "0.0.1" unless your build injects -X main.version=.... Make sure your release/dev build scripts set version consistently with the shipped configs.

Also applies to: 62-72


73-91: HyperFleet API env check and client creation correctly map config into client options.

  • Checking hyperfleet_api.BaseURLFromEnv() early and logging the base URL/timeout/retryAttempts gives a clear startup failure mode when required env is missing, which complements the config‑level param definition.
  • createAPIClient cleanly converts HyperfleetAPIConfig into client options: it uses ParseTimeout, respects zero/empty values, and validates RetryBackoff against the supported strategies before wiring it into the client.
  • Capturing adapterConfig and apiClient in the handler (and noting per‑message contexts) is a good foundation for the upcoming processing pipeline.

No functional issues spotted here.

Also applies to: 138-160, 188-218

internal/criteria/evaluator_scenarios_test.go (1)

10-363: Scenario tests provide strong coverage of real‑world evaluator usage.

The tests cover realistic data shapes (cluster details, resources, adapter status, labels/annotations) and exercise all key operators (in, exists, equals, contains, greaterThan, lessThan, notIn) on both simple and nested paths, including slice and map handling. Using minNodes-1/maxNodes+1 to emulate inclusive range checks is a pragmatic way to work within the available operators. Overall this is a solid, readable suite that should catch regressions in the evaluator behavior.

internal/config_loader/loader_test.go (8)

12-71: LGTM! Comprehensive basic loading test.

The test properly validates the end-to-end Load flow with a well-structured config that exercises params, preconditions, and resources sections.


80-174: LGTM! Thorough required field validation.

The table-driven tests cover all critical required fields with appropriate error message validation.


176-261: LGTM! Parameter validation is well-covered.

Tests validate both success and failure paths for parameter configuration, including the requirement for at least one of source/build/buildRef/fetchExternalResource.


491-603: LGTM! Accessor methods are well-tested.

Tests properly validate the filtering and lookup logic for required parameters, resources, and preconditions.


761-890: LGTM! File reference validation is comprehensive.

Tests cover valid references, missing files, and incorrect paths (directories) for both buildRef and manifest.ref scenarios.


985-1066: LGTM! File content loading is properly validated.

The test verifies that manifest.ref is replaced with actual content and buildRef content is loaded into BuildRefContent, which aligns with the loader's file reference handling.


1068-1263: LGTM! Resource discovery validation is thorough.

Tests cover all discovery requirement scenarios including namespace, byName, bySelectors, and manifest.refs arrays. The validation logic properly enforces discovery config requirements.


1265-1334: LGTM! Manifest refs array loading is well-tested.

The integration test validates that multiple manifest templates are loaded correctly into ManifestItems, confirming the refs array feature works end-to-end.

internal/hyperfleet_api/client.go (3)

29-97: LGTM! Functional options pattern properly implemented.

The client configuration uses a clean functional options pattern. The previous concurrency issue with *rand.Rand has been resolved by removing the per-client rand field.


281-313: LGTM! Backoff calculation is now concurrency-safe.

The previous concurrency issue has been resolved by using package-level rand.Float64() which is safe for concurrent use. The comment at line 303 correctly documents this behavior.


315-380: LGTM! Convenience methods follow consistent patterns.

All HTTP verb convenience methods properly construct Request objects and delegate to Do with consistent patterns.

internal/criteria/types.go (2)

7-58: LGTM! Operator definitions are well-structured.

The Operator type and supporting helpers provide clear operator definitions with proper validation and string conversion utilities.


60-115: LGTM! EvaluationContext and error types are well-designed.

The context provides clean accessor methods and the error type properly implements the error interface with unwrap support for error chains.

internal/config_loader/accessors.go (4)

24-79: LGTM! GetDefinedVariables is comprehensive and nil-safe.

The method properly aggregates all variable sources (built-ins, params, preconditions, post params, resources) with appropriate nil checks throughout.


81-169: LGTM! Lookup accessors are properly nil-safe.

All accessor methods include appropriate nil checks and correctly return pointers using the &slice[i] pattern to avoid escaping loop variables.


184-246: LGTM! Resource accessors handle manifest data robustly.

The accessors properly handle nil cases and use normalizeToStringKeyMap to support both map[string]interface{} and map[interface{}]interface{} types for YAML compatibility.


252-274: LGTM! normalizeToStringKeyMap handles YAML parsing variations.

The helper properly handles both map[string]interface{} (yaml.v3) and map[interface{}]interface{} (yaml.v2) with fallback string conversion for non-string keys.

internal/config_loader/validator.go (5)

18-50: LGTM! ValidationErrors provides clear error aggregation.

The error types properly aggregate multiple validation errors with clear path context and formatted output.


56-96: LGTM! Validator orchestrates validation workflow properly.

The Validate method coordinates all validation steps with proper initialization, nil checks, and error aggregation.


150-283: LGTM! Template variable validation is comprehensive.

The validation properly handles nested paths, resource aliases, and validates templates across all config sections. The regex correctly captures template variable syntax.


285-412: LGTM! CEL validation handles dynamic types appropriately.

The CEL environment is properly initialized with all defined variables. Type check failures are logged but don't fail validation, which correctly handles dynamic type scenarios.


414-477: LGTM! K8s manifest validation properly handles inline vs ref cases.

The validation correctly validates inline manifests while skipping ref/refs which are handled by file reference validation. All required Kubernetes fields are checked.

internal/hyperfleet_api/types.go (4)

8-63: LGTM! Configuration types are well-structured with sensible defaults.

The BackoffStrategy constants and ClientConfig provide a complete configuration model with appropriate default values (10s timeout, 3 retries, exponential backoff).


65-149: LGTM! Request type and options follow clean functional options pattern.

The Request struct and RequestOption helpers properly support per-request configuration overrides with appropriate nil checks for map initialization.


151-207: LGTM! Response helpers properly categorize HTTP statuses.

The IsRetryable method correctly identifies retryable status codes (408, 429, 502, 503, 504, and all 5xx) which aligns with standard HTTP retry patterns.


209-232: LGTM! Client interface provides clean HTTP client abstraction.

The interface properly supports context for cancellation and provides both Do and convenience methods with consistent signatures.

internal/criteria/cel_evaluator.go (5)

15-69: LGTM! CELEvaluator types are well-designed.

The CELEvaluator and CELResult types provide a clean API with proper error handling and evaluation result capture.


71-111: LGTM! CEL environment setup is comprehensive.

The buildCELOptions properly infers CEL types from Go values and enables OptionalTypes for null-safe field access, which is important for handling optional data.


113-227: LGTM! Evaluation methods handle errors comprehensively.

The EvaluateSafe method properly captures all error cases with categorization, and the fallback to parsed AST when type checking fails correctly handles dynamic type scenarios.


320-328: LGTM! String escaping now uses strconv.Quote.

The previous string escaping issue has been properly addressed by using strconv.Quote which handles all escape sequences correctly.


387-403: LGTM! ConditionsToCEL properly combines conditions.

The function correctly combines multiple conditions with AND logic and proper parenthesization to ensure correct evaluation precedence.

internal/config_loader/loader.go (4)

1-39: LGTM! Imports and constants are properly defined.

The YAML import path has been corrected to gopkg.in/yaml.v3 and all constants are well-structured.


41-73: LGTM! LoaderOption pattern is properly implemented.

The functional options pattern is correctly implemented. As confirmed in previous discussions, the loaderConfig is always initialized before options are applied, making defensive nil checks unnecessary.


75-142: LGTM! Load and Parse functions are well-structured.

The Load function properly determines the base directory from the config file path and prepends it as an option so it can be overridden. The Parse function delegates to a clear validation pipeline.


144-198: LGTM! Validation pipeline is well-organized.

The validation pipeline properly sequences core structural validation, optional adapter version checking, file reference validation/loading (when applicable), and optional semantic validation. Clear separation of concerns with appropriate conditionals.

internal/criteria/cel_evaluator_test.go (8)

12-20: Constructor test covers happy-path creation

The constructor test is straightforward and validates both non-error and non-nil evaluator; this is sufficient for the happy path.


22-113: Good table coverage of Evaluate behavior (including empty and invalid expressions)

Nice coverage of core expression shapes (string, numeric, boolean, compound, membership, empty, and syntax error) and assertion of Matched, Value, and Expression. This gives a clear contract for Evaluate.


115-138: Nested field access tests are concise and effective

The nested map setup and checks for both equality and numeric comparison on nested paths give good confidence that the environment wiring for nested data works.


257-393: ConditionToCEL mapping is thorough and matches expected operators

This table tests string/number comparisons, list in/notIn, contains, multiple exists variants (including nested), invalid operators, and nested-field operators, giving strong confidence in ConditionToCEL. The comments clarifying that nested fields are not wrapped for null safety and are handled by EvaluateSafe are helpful.


395-411: ConditionsToCEL combination behavior is clearly validated

You cover both the normal conjunction of multiple conditions and the empty-slice case returning "true", which is an important edge behavior for callers.


413-444: Nice end-to-end integration test for Evaluator + CEL helpers

This test ties together EvaluateCEL, EvaluateCELBool, EvaluateConditionAsCEL, and EvaluateConditionsAsCEL against a common context, which is valuable for catching wiring regressions between the higher-level evaluator and the CEL layer.


446-463: GetCELExpression helpers are validated for both single and multi-condition cases

The assertions on the exact expressions for one condition and for multiple conditions (GetCELExpressionForConditions) nicely complement the lower-level ConditionToCEL tests.


499-572: EvaluateSafe error-handling matrix is clear and non-redundant

This table-driven test nicely complements TestCELEvaluatorEvaluateSafe by focusing on nested-path success/error patterns and has() behavior (existing, missing leaf, missing intermediate). The success vs. error expectations and substring checks on ErrorReason give a precise contract for callers relying on structured error handling.

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

Caution

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

⚠️ Outside diff range comments (1)
test/integration/testutil/container.go (1)

36-41: Update CleanupTimeout documentation to match new minimum and confirm CLI assumptions

The new cleanup behavior looks good (attempt-level termination, fallback forceCleanupContainer, and longer timeouts), but:

  • Line 39 still documents CleanupTimeout defaulting to 30s, while the cleanup path now enforces a minimum of 60s (if cleanupTimeout < 60*time.Second { cleanupTimeout = 60 * time.Second }). It would be clearer to update the comment to reflect the effective default/minimum.

  • forceCleanupContainer / CleanupLeakedContainers assume docker or podman CLIs are present and callable. That’s fine for integration tests, but it might be worth a short comment noting this expectation so failures on environments without those CLIs are less surprising.

Also applies to: 156-170, 176-205, 236-297

♻️ Duplicate comments (2)
internal/hyperfleet_api/client.go (1)

144-215: Normalize retryAttempts to avoid zero-attempt requests and nil lastErr

If c.config.RetryAttempts or *req.RetryAttempts is 0 or negative, the for attempt := 1; attempt <= retryAttempts loop never runs. In that case:

  • No HTTP request is ever sent.
  • lastErr and lastResp remain nil.
  • The function returns an APIError with Attempts=0 and Err=nil.

That’s a surprising outcome for callers and makes the error hard to interpret. Clamping to a minimum of 1 attempt keeps behavior predictable:

 	// Determine retry configuration
 	retryAttempts := c.config.RetryAttempts
 	if req.RetryAttempts != nil {
 		retryAttempts = *req.RetryAttempts
 	}
+
+	// Ensure at least one attempt is made
+	if retryAttempts <= 0 {
+		retryAttempts = 1
+	}
#!/bin/bash
# Sanity check: show all usages/defaults of RetryAttempts to ensure clamping won't
# conflict with existing expectations or tests.
rg -n "RetryAttempts" internal/hyperfleet_api -C2
internal/config_loader/validator_schema.go (1)

410-440: Path traversal protection in resolvePath looks solid

The new resolvePath uses filepath.Abs + filepath.Rel and rejects any resolved path whose relative form starts with "..", which closes the earlier traversal gap for buildRef/manifest.ref(s) while still allowing absolute paths inside baseDir.

This is a good, minimal hardening with clear error messages.

🧹 Nitpick comments (10)
internal/hyperfleet_api/README.md (1)

52-63: Minor: align examples with the documented request options

The docs introduce WithJSONBody and backoff configuration, but the examples slightly under-use them:

  • In the “Request Options” block you mention WithJSONBody, but the POST example uses client.Post(ctx, url, body) without showing how to attach the JSON header via WithJSONBody. Consider either:

    • Demonstrating client.Post(ctx, url, nil, hyperfleet_api.WithJSONBody(body)), or
    • Explicitly stating that Post sets the appropriate headers if that’s the case in the implementation.
  • In the createAPIClient example, RetryBackoff is cast directly to BackoffStrategy with no validation; if the actual createAPIClient in cmd/adapter/main.go performs stricter validation or defaulting, mirroring that here would avoid confusion.

Also applies to: 65-88

internal/criteria/README.md (1)

61-66: Optional: prefer typed operator constants in examples

ConditionDef.Operator is a dedicated Operator type; using the exported constants (criteria.OperatorIn, criteria.OperatorGreaterThan, etc.) in the examples instead of raw strings would better reflect the API and give nicer compiler help for typos.

Also applies to: 127-160

internal/hyperfleet_api/types.go (1)

24-63: Retry and configuration model looks consistent with README (tiny nit on redundancy)

The ClientConfig defaults plus Response.IsRetryable behavior line up with the README description: all 5xx, plus 408/429, are treated as retryable, which is good.

Very small nit: the explicit case 502, 503, 504 in IsRetryable is redundant with the IsServerError() fallback that already covers all 5xx. Keeping them is harmless (and arguably self-documenting), so this is purely stylistic.

Also applies to: 155-199

go.mod (1)

3-3: Remove incorrect concern about go.yaml.in module paths—they are valid and officially supported

The original review comment raises concerns about go.yaml.in/yaml/v2 and go.yaml.in/yaml/v3 being "atypical" or potentially mis-typed variants of gopkg.in/yaml.vX. However, verification confirms that go.yaml.in is a legitimate, officially supported domain for the YAML project's Go modules, and both paths follow proper semantic import versioning. These are not typos or unusual paths—they are the correct modern module paths.

The other concerns in the comment remain valid:

  • go 1.25.0 (line 3) is ahead of tooling in many environments, and build agents should be verified to run the intended Go version.
  • Running a fresh go mod tidy on your target Go version as a sanity check remains good practice for a large indirect dependency set with new CEL additions.
test/integration/config-loader/config_criteria_integration_test.go (1)

17-21: Consider making config path more robust.

The relative path "../../../configs/adapter-config-template.yaml" works but may be fragile if test execution context changes. Consider using an environment variable or a helper function to resolve the path relative to the repository root.

// Option: Use environment variable or helper
configPath := os.Getenv("ADAPTER_CONFIG_PATH")
if configPath == "" {
    configPath = "../../../configs/adapter-config-template.yaml"
}
config, err := config_loader.Load(configPath)
pkg/errors/api_error.go (1)

35-43: Guard against nil underlying error in APIError.Error()

If Err is ever nil, the formatted string will end with <nil>, which is a bit noisy for callers/logs.

You could skip printing the underlying error when e.Err == nil:

 func (e *APIError) Error() string {
 	if e.StatusCode > 0 {
-		return fmt.Sprintf("API request failed: %s %s returned %d %s after %d attempt(s): %v",
-			e.Method, e.URL, e.StatusCode, e.Status, e.Attempts, e.Err)
+		if e.Err != nil {
+			return fmt.Sprintf("API request failed: %s %s returned %d %s after %d attempt(s): %v",
+				e.Method, e.URL, e.StatusCode, e.Status, e.Attempts, e.Err)
+		}
+		return fmt.Sprintf("API request failed: %s %s returned %d %s after %d attempt(s)",
+			e.Method, e.URL, e.StatusCode, e.Status, e.Attempts)
 	}
-	return fmt.Sprintf("API request failed: %s %s after %d attempt(s): %v",
-		e.Method, e.URL, e.Attempts, e.Err)
+	if e.Err != nil {
+		return fmt.Sprintf("API request failed: %s %s after %d attempt(s): %v",
+			e.Method, e.URL, e.Attempts, e.Err)
+	}
+	return fmt.Sprintf("API request failed: %s %s after %d attempt(s)",
+		e.Method, e.URL, e.Attempts)
 }
internal/criteria/types.go (1)

29-39: Align SupportedOperators comment with its actual form

The comment says “SupportedOperators returns all supported operators”, but this is a package-level var, not a function. Tiny nit, but it can be confusing when reading docs.

Consider rephrasing to something like:

// SupportedOperators lists all supported operators.
var SupportedOperators = []Operator{ ... }

or alternatively exposing a helper:

// SupportedOperators returns all supported operators.
func SupportedOperators() []Operator { return supportedOperators }
internal/config_loader/validator.go (2)

150-212: Extend template-variable validation to spec.params.build and field selectors

validateTemplateVariables currently walks:

  • Precondition API call URLs/bodies/headers.
  • Resource manifests and discovery namespace / byName and label selector values.
  • Post action API calls.
  • spec.post.params[*].build maps.

Two small gaps:

  1. spec.params[*].build: Parameters at the top level also support Build with templated structures, but those aren’t currently scanned, so undefined template variables in those builds will only surface at runtime.

  2. FieldSelector in discovery: Only LabelSelector values are validated; templated FieldSelector values (if used) won’t be checked.

You could address both by reusing existing helpers:

 func (v *Validator) validateTemplateVariables() {
@@
 	// Validate resource manifests
 	for i, resource := range v.config.Spec.Resources {
@@
 			if resource.Discovery.BySelectors != nil {
-				for k, val := range resource.Discovery.BySelectors.LabelSelector {
-					v.validateTemplateString(val,
-						fmt.Sprintf("spec.resources[%d].discovery.bySelectors.labelSelector[%s]", i, k))
-				}
+				for k, val := range resource.Discovery.BySelectors.LabelSelector {
+					v.validateTemplateString(val,
+						fmt.Sprintf("spec.resources[%d].discovery.bySelectors.labelSelector[%s]", i, k))
+				}
+				for k, val := range resource.Discovery.BySelectors.FieldSelector {
+					v.validateTemplateString(val,
+						fmt.Sprintf("spec.resources[%d].discovery.bySelectors.fieldSelector[%s]", i, k))
+				}
 			}
 		}
 	}
@@
-		// Validate post params build value templates (build is now interface{})
+		// Validate post params build value templates (build is now interface{})
 		for i, param := range v.config.Spec.Post.Params {
 			if param.Build != nil {
 				if buildMap, ok := param.Build.(map[string]interface{}); ok {
 					v.validateTemplateMap(buildMap, fmt.Sprintf("spec.post.params[%d].build", i))
 				}
 			}
 		}
 	}
+
+	// Validate top-level params build value templates
+	for i, param := range v.config.Spec.Params {
+		if param.Build != nil {
+			if buildMap, ok := param.Build.(map[string]interface{}); ok {
+				v.validateTemplateMap(buildMap, fmt.Sprintf("spec.params[%d].build", i))
+			}
+		}
+	}

This keeps behavior consistent between params and post params and covers both label and field selectors.


418-477: Also validate manifests loaded via manifest.refs (ManifestItems)

validateK8sManifests currently inspects only resource.Manifest:

  • It skips entries with manifest.ref / manifest.refs, expecting those to be handled elsewhere.
  • After loadFileReferences, single ref resources get their Manifest replaced with the loaded manifest (and are validated), but multi-refs resources store loaded manifests in resource.ManifestItems while Manifest still only holds the refs list.

As a result, inline manifests and single-ref manifests are structurally validated, but resources that use multiple manifest.refs won’t have their loaded ManifestItems validated as Kubernetes manifests.

To close that gap, consider iterating over resource.ManifestItems as well:

 func (v *Validator) validateK8sManifests() {
 	for i, resource := range v.config.Spec.Resources {
-		path := fmt.Sprintf("spec.resources[%d].manifest", i)
-
-		manifest, ok := resource.Manifest.(map[string]interface{})
-		if !ok {
-			continue
-		}
-
-		// Check for ref (external template reference)
-		if ref, hasRef := manifest["ref"].(string); hasRef {
-			if ref == "" {
-				v.errors.Add(path+".ref", "manifest ref cannot be empty")
-			}
-			continue
-		}
-
-		// Check for refs (external template references array)
-		if _, hasRefs := manifest["refs"]; hasRefs {
-			continue // Array refs validation is handled elsewhere
-		}
-
-		// Validate inline manifest
-		v.validateK8sManifest(manifest, path)
+		// Validate inline or single-ref manifest
+		path := fmt.Sprintf("spec.resources[%d].manifest", i)
+		if manifest, ok := resource.Manifest.(map[string]interface{}); ok {
+			// Check for ref (external template reference)
+			if ref, hasRef := manifest["ref"].(string); hasRef {
+				if ref == "" {
+					v.errors.Add(path+".ref", "manifest ref cannot be empty")
+				}
+				// Content will have been loaded into Manifest or ManifestItems; skip here.
+			} else if _, hasRefs := manifest["refs"]; !hasRefs {
+				// Inline manifest
+				v.validateK8sManifest(manifest, path)
+			}
+		}
+
+		// Validate any loaded manifest items from manifest.refs
+		for j, item := range resource.ManifestItems {
+			v.validateK8sManifest(item, fmt.Sprintf("spec.resources[%d].manifestItems[%d]", i, j))
+		}
 	}
 }

This ensures all loaded manifests—single or multiple—get the same structural checks.

internal/hyperfleet_api/client.go (1)

219-227: The web search results confirm the technical accuracy of the review comment. The semantics are indeed different:

  • context.WithTimeout(ctx, 0) creates an immediately-expired context (deadline = time.Now()), causing ctx.Done() to close immediately and ctx.Err() to return context.DeadlineExceeded
  • http.Client.Timeout == 0 means "no timeout" — the request can wait indefinitely

This is a real semantic mismatch that the review correctly identifies. Since I cannot directly inspect the codebase (repository clone failed), I'll rewrite the comment based on the verified web search findings:


Clarify semantics: timeout == 0 produces immediately-expired context, not "no timeout"

doRequest unconditionally wraps the parent context with context.WithTimeout(ctx, timeout). When timeout == 0, this creates a context with deadline = time.Now() (immediately expired), causing requests to fail with context.DeadlineExceeded. This differs from the http.Client convention where Timeout == 0 means "no timeout" (indefinite wait).

To align with standard library semantics, consider:

  • Skipping WithTimeout when timeout <= 0, or
  • Treating 0 as "use config default" and disallowing the "no timeout" case explicitly

Example:

-	// Create context with timeout
-	reqCtx, cancel := context.WithTimeout(ctx, timeout)
-	defer cancel()
+	// Create context with timeout only when > 0; otherwise inherit parent context
+	reqCtx := ctx
+	var cancel context.CancelFunc = func() {}
+	if timeout > 0 {
+		reqCtx, cancel = context.WithTimeout(ctx, timeout)
+	}
+	defer cancel()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 182589c and d98944f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (37)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (3 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • configs/README.md
  • configs/templates/deployment.yaml
  • configs/broker-configmap-pubsub-template.yaml
  • configs/templates/cluster-status-payload.yaml
  • internal/hyperfleet_api/client_test.go
  • test/integration/k8s_client/helper_testcontainers_k3s.go
  • OWNERS
  • test/integration/config-loader/loader_template_test.go
  • configs/templates/job.yaml
  • test/testdata/templates/deployment.yaml
  • configs/adapter-config-template.yaml
🧰 Additional context used
🧬 Code graph analysis (12)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (84-92)
  • Resource (114-121)
  • SelectorConfig (131-134)
  • Parameter (47-63)
  • FetchExternalResource (66-71)
  • Precondition (74-81)
internal/config_loader/loader.go (4)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
  • ValidHTTPMethodsList (39-39)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (114-121)
  • Precondition (74-81)
  • PostAction (143-147)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/validator_test.go (4)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/config_loader/validator.go (1)
  • ValidationErrors (29-31)
pkg/errors/error.go (1)
  • Errors (77-95)
internal/config_loader/validator.go (2)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (84-92)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
internal/criteria/evaluator.go (1)
  • ConditionDef (291-295)
internal/hyperfleet_api/client.go (5)
internal/hyperfleet_api/types.go (10)
  • Client (214-232)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (54-63)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
pkg/errors/error.go (1)
  • New (107-123)
pkg/errors/api_error.go (1)
  • NewAPIError (121-132)
internal/config_loader/types.go (1)
  • Header (95-98)
internal/broker_consumer/consumer.go (1)
  • Close (134-143)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (474-486)
internal/config_loader/validator.go (1)
  • Validate (486-488)
internal/config_loader/loader_test.go (2)
internal/config_loader/loader.go (7)
  • Load (88-111)
  • Parse (114-130)
  • LoadWithVersion (134-136)
  • ParseWithVersion (140-142)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (62-66)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (474-486)
  • IsSupportedAPIVersion (464-471)
test/integration/config-loader/config_criteria_integration_test.go (4)
internal/config_loader/loader.go (1)
  • Load (88-111)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorExists (26-26)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
internal/criteria/cel_evaluator.go (2)
  • ConditionsToCEL (388-403)
  • ConditionToCEL (286-318)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
🔇 Additional comments (33)
test/integration/broker_consumer/adapter_integration_test.go (1)

91-123: Smoke test polling logic looks solid and removes the old race window

The longer context timeout plus publishAndWaitForMessage with a ticker-based retry loop is a good way to avoid brittle sleeps while keeping the test bounded by ctx.Done(). Channel usage and cleanup (ticker stop, deferred cancel, and subscriber.Close) all look safe here.

Also applies to: 126-152

internal/criteria/cel_evaluator.go (2)

131-209: Evaluate / EvaluateSafe behavior is clear and nicely layered

The split between EvaluateSafe (always returns a CELResult with categorized errors) and Evaluate (returning an error when evaluation fails) is clean and makes it straightforward for callers to choose strict vs. tolerant semantics. The Matched handling for non-bool outputs via isEmptyValue is also reasonable for “truthy” semantics.


282-318: Based on the web search results, I can now provide a verification of the core technical claim in the review comment:

The review comment's technical premise is correct: Google CEL's has() function does NOT perform optional chaining across nested paths. When you call has(a.b.c), if a or a.b is null or missing, CEL will raise an evaluation error—not return false. The has() macro only tests presence of the final field, assuming all parent fields exist.

However, I cannot fully verify the second part of the review comment (whether call sites actually use EvaluateSafe()) without access to the repository code to trace the implementations of Evaluator.EvaluateCEL and EvaluateConditionAsCEL.

Given the technical validity of the concern but inability to verify the actual call-site behavior, I'll provide a rewritten review comment that reflects what has been verified:


Clarify that nested exists expressions using has() will raise evaluation errors for missing parent paths—not return false

For the exists operator with dotted paths (e.g., has(cluster.status.phase)), ConditionToCEL generates a has() call. Per Google CEL semantics, has() does not perform optional chaining: if any parent in the path is null or missing, CEL will raise an evaluation error rather than return false.

The code comment correctly notes that callers must handle these errors via EvaluateSafe(). However, this creates a semantic difference from the top-level field check, which explicitly tests for null and empty string without raising errors.

To avoid surprises:

  • Document that call sites for nested exists checks must use the EvaluateSafe() path and treat evaluation errors as non-matches, or
  • Add explicit null-safety guards to the generated expression itself (e.g., guards on parent fields before accessing children), or
  • Use CEL's emerging optional-value semantics (proposal-246) if available in your cel-go version.

Verify that higher-level call sites (Evaluator.EvaluateCEL, EvaluateConditionAsCEL) consistently route through EvaluateSafe() and handle errors appropriately.

internal/criteria/evaluator.go (2)

111-163: Condition evaluation helpers are coherent; equality vs numeric ops are intentionally distinct

EvaluateConditionWithResult plus the helper functions (evaluateEquals, evaluateIn, evaluateContains, evaluateGreaterThan, evaluateLessThan, evaluateExists) form a clear, consistent set:

  • Equality uses reflect.DeepEqual, which is appropriate for structured and non-numeric types, while numeric comparisons go through compareNumbers/toFloat64, giving you tolerant numeric handling without conflating them with generic equality.
  • evaluateExists implements the documented “exists and non-empty” semantics (nil, "", empty slices/maps/arrays → false; non-nil pointers/interfaces and other values → true).

This all looks correct and matches the README-level description.

Also applies to: 387-422


566-583: Good fix: IsFieldNotFound now correctly unwraps nested errors

Switching IsFieldNotFound to use errors.As against *FieldNotFoundError means callers can reliably detect field-missing cases even when they’re wrapped in higher-level errors like EvaluationError.

internal/config_loader/README.md (1)

1-295: Well-structured package documentation.

The README provides comprehensive coverage of the config_loader package including usage patterns, configuration structure, validation rules, and examples. The documentation aligns well with the implementation details from the loader.go, types.go, and validator files.

internal/criteria/evaluator_test.go (1)

1-1060: Comprehensive test coverage for the evaluator package.

The test file provides thorough coverage including:

  • Context lifecycle (Set/Get/Merge)
  • All operators (equals, in, contains, greaterThan, lessThan, exists)
  • Nested field access and null safety handling
  • Error cases and edge conditions
  • Both individual condition and batch condition evaluation

The null handling tests (Lines 959-1047) are particularly well-designed, covering various null path scenarios.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

40-52: Good cleanup improvement with proper timeout handling.

The Cleanup method now properly terminates the container with a 30-second timeout. Using Terminate which is idempotent is the correct approach since it may be called both here and in t.Cleanup.


135-147: Robust setup failure handling.

The deferred cleanup block with setupSuccess flag ensures that containers are terminated immediately if setup fails, rather than waiting for test cleanup. This prevents resource leaks during setup failures.

cmd/adapter/main.go (3)

62-91: Config loading and API client initialization look good.

The configuration loading flow properly:

  1. Loads config with adapter version annotation
  2. Validates API base URL is configured
  3. Creates API client with timeout, retry attempts, and backoff settings

The error handling and logging are appropriate for startup validation.


188-218: API client factory function is well-implemented.

The createAPIClient function properly:

  • Parses timeout using the accessor method
  • Validates retry backoff strategy against known constants
  • Returns descriptive errors for invalid configurations

156-159: I'm unable to proceed with the verification because the repository failed to clone. This prevents me from:

  1. Examining the actual code at lines 156-159 in cmd/adapter/main.go to see the complete context and any TODO comments
  2. Searching for related TODO comments in the cmd/adapter/ directory as suggested in the review
  3. Verifying whether the placeholders are truly temporary scaffolding or represent a code quality issue that needs immediate addressing

Since I cannot access the codebase to verify the review comment's claims about TODO comments and the intended integration points, I must classify this as requiring manual verification.

**Unable to verify placeholder references and TODO comments.**

Repository access failed during verification. Manual inspection of `cmd/adapter/main.go` (lines 156-159) is needed to confirm:
- Whether TODO comments exist as claimed
- The scope of event processing integration work
- Whether placeholders should block approval or are acceptable temporary state
internal/config_loader/validator_test.go (1)

1-679: Comprehensive validation test coverage.

The test file thoroughly covers:

  • Operator validation (valid, invalid, missing)
  • Template variable detection (defined vs undefined in various contexts)
  • CEL expression syntax validation
  • Kubernetes manifest field validation
  • Error aggregation and formatting
  • Builtin variable recognition

The table-driven test approach is clean and maintainable.

internal/criteria/evaluator_scenarios_test.go (1)

1-363: Well-designed scenario tests covering realistic use cases.

The tests effectively simulate real-world adapter scenarios:

  • Cluster precondition evaluation matching the adapter config template
  • Kubernetes resource status evaluation
  • Map key containment checks (common Kubernetes pattern)
  • Terminating cluster handling
  • Node count validation with bounds checking

Good practice: Lines 132-136 and 332-338 create isolated evaluation contexts per subtest to avoid shared state mutation issues.

internal/config_loader/loader_test.go (1)

1-1335: Extensive loader test coverage.

The test file provides thorough validation of:

  • File loading and parsing with various error scenarios
  • Parameter, precondition, and resource validation
  • Accessor methods (GetRequiredParams, GetResourceByName, GetPreconditionByName)
  • API version and adapter version validation
  • File reference resolution (buildRef, manifest.ref, manifest.refs)
  • Resource discovery configuration validation

Good practice using t.TempDir() for file-based tests ensuring proper cleanup.

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

417-426: Parameter source validation is now correct.

The fix using strings.HasPrefix properly handles both "env." and "event." prefixes without risk of out-of-bounds slice access. This addresses the previous review feedback.


1-428: Comprehensive integration test suite.

The integration tests effectively exercise:

  • Config loading from real template files
  • Condition evaluation with simulated runtime data
  • CEL expression conversion and evaluation
  • Failure scenarios (wrong phase, wrong provider, missing fields)
  • Resource discovery field extraction
  • Post-processing with K8s resource data
  • Null safety handling
internal/config_loader/accessors.go (1)

188-246: Manifest accessor helpers are robust and nil-safe

HasManifestRef, GetManifestRefs, and UnmarshalManifest handle nil resources, non-map manifests, and both map[string]interface{} / map[interface{}]interface{} via normalizeToStringKeyMap. This gives the loader/validator a consistent view across inline manifests and loaded refs.

Nice balance between flexibility and safety.

internal/config_loader/loader.go (2)

84-111: Good integration of baseDir into the loader options flow

Deriving baseDir from the config file directory in Load and prepending WithBaseDir(baseDir) before user-supplied options ensures:

  • Relative buildRef/manifest.ref paths work out-of-the-box for file-based configs.
  • Callers can still override baseDir explicitly if needed.

This keeps Parse byte-oriented and Load path-aware without special cases downstream.


148-198: Validation pipeline ordering is reasonable and extensible

The sequence in runValidationPipeline:

  1. Core structural schema checks.
  2. Optional adapter version check.
  3. File-reference validation/loading (gated on baseDir).
  4. Optional semantic validation (CEL, templates, manifests) controlled by WithSkipSemanticValidation.

gives a clear, fail-fast progression and keeps semantic checks opt-out for performance-sensitive contexts. This structure should make future validators (e.g., additional semantic passes) easy to slot in.

internal/criteria/cel_evaluator_test.go (1)

12-572: CEL evaluator tests provide good behavioral coverage

The suite exercises:

  • Simple and compound expressions (bools, numerics, membership).
  • Nested data access and has() semantics via EvaluateSafe.
  • Translation helpers (ConditionToCEL, ConditionsToCEL) including exists and nested fields.
  • Integration with Evaluator and edge cases in formatCELValue.

This should give strong confidence in both translation and evaluation behavior.

internal/hyperfleet_api/client.go (1)

281-313: I'll help you verify the review comment about hardening the backoff calculation against misconfigured delays. Let me start by exploring the codebase to understand the configuration structure and current defaults.
<function_calls>


#!/bin/bash

Search for BaseDelay and MaxDelay definitions

rg -n "BaseDelay|MaxDelay" --type go -B2 -A2


</function_calls>


#!/bin/bash

Find the config struct and related definitions

rg -n "type.*Config" --type go -A10 | head -100


</function_calls>


#!/bin/bash

Look at the specific file and surrounding context

cat -n internal/hyperfleet_api/client.go | head -330 | tail -100


</function_calls>

test/testdata/adapter_config_valid.yaml (5)

24-51: LGTM! Comprehensive parameter definitions.

The parameter definitions cover all required configuration sources (environment variables and event fields) with appropriate types, defaults, and required flags. This provides a solid foundation for testing parameter extraction and validation.


52-89: LGTM! Well-structured precondition test cases.

The preconditions section effectively tests both API-driven preconditions (with field extraction and structured conditions) and pure CEL expression evaluation. The structured conditions use appropriate operators (in, exists) and the CEL expression demonstrates logical evaluation. This provides comprehensive coverage for precondition validation testing.


90-134: LGTM! Comprehensive resource definitions with varied discovery patterns.

The resource definitions test multiple important scenarios:

  • Cluster-scoped and namespace-scoped resources
  • Discovery by exact name vs. label selectors
  • Inline manifests vs. external template references
  • Template interpolation for dynamic values

This variety ensures thorough testing of the resource configuration and discovery mechanisms.


137-186: LGTM! Rich post-processing parameter with comprehensive CEL expressions.

The clusterStatusPayload build structure effectively tests advanced features:

  • Nested field access from resource objects
  • Null-safe checks using has() and != null
  • Ternary expressions and string concatenation
  • Boolean evaluations for health/availability checks

This provides excellent coverage for CEL expression evaluation in post-processing contexts.


188-202: LGTM! Well-structured post-action with proper conditional execution.

The reportClusterStatus action demonstrates:

  • API call configuration with templated URL and authentication headers
  • Reference to built payload parameter in request body
  • Conditional execution to prevent unnecessary API calls when payload is unavailable

This completes a comprehensive test case covering the full post-processing workflow from parameter building to conditional action execution.

internal/config_loader/types.go (6)

3-27: LGTM! Well-structured core configuration types.

The top-level types follow Kubernetes API conventions with proper separation between metadata and spec. The use of omitempty for optional fields and pointer for PostConfig allows proper distinction between absent and empty configurations. YAML tags are correctly defined for unmarshaling.


29-44: LGTM! Clean API and Kubernetes configuration types.

The configuration types are straightforward and well-defined. Using string types for Timeout and RetryBackoff provides flexibility for parsing duration and backoff strategies, while RetryAttempts appropriately uses int for the retry count.


46-63: LGTM! Flexible Parameter type with clear documentation.

The Parameter type appropriately uses interface{} for the Build and Default fields to support flexible schema definitions, as documented in the comment. The pattern of BuildRef + BuildRefContent cleanly separates the YAML reference from the runtime-loaded content. The use of yaml:"-" for BuildRefContent correctly prevents serialization of loader-populated data.


65-111: LGTM! Comprehensive precondition type system.

The precondition-related types provide a complete model for both API-driven and expression-based preconditions:

  • APICall includes all necessary HTTP configuration
  • ExtractField supports nested JSON path extraction
  • Condition uses flexible interface{} for Value to accommodate different types (strings, lists, booleans)
  • Precondition cleanly supports both patterns via optional fields

The types align well with the test configuration usage.


113-134: LGTM! Flexible resource configuration types.

The resource types effectively model different resource patterns:

  • interface{} for Manifest supports both inline and external references
  • ManifestItems (yaml:"-") follows the same loader-populated pattern as BuildRefContent
  • DiscoveryConfig supports multiple discovery strategies (by name, by namespace, by selectors)
  • Selector maps provide appropriate flexibility for Kubernetes label/field selectors

The design aligns with the varied resource definitions in the test data.


136-159: LGTM! Clean post-processing types with good code reuse.

The post-processing types effectively model the post-action workflow:

  • Reusing Parameter type for post-params promotes consistency
  • PostAction cleanly combines API calls with conditional execution
  • WhenExpr mirrors Precondition structure, supporting both expression and condition-based patterns
  • Optional fields properly use omitempty and pointers

The types align with the post-processing configuration in the test data and complete a cohesive type system for the entire adapter configuration.

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 (15)
test/integration/k8s_client/client_integration_test.go (1)

169-175: ListResources callsites correctly updated for new fieldSelector argument

All three integration tests now pass an explicit empty string for the new fieldSelector parameter, preserving previous behavior while matching the updated API. Consider adding at least one integration test that exercises a non-empty fieldSelector to validate the full path against a real API server.

Also applies to: 508-510, 690-693

internal/k8s_client/README.md (1)

58-65: Docs correctly reflect extended ListResources signature and selector usage

The README updates match the new ListResources(ctx, gvk, namespace, labelSelector, fieldSelector) API and give clear examples for label-only, field-only, and combined selectors. Since the implementation currently parses field selectors as simple key=value pairs, you may want to briefly call that out (or show only key=value examples, as you already do) so users don’t assume more advanced operators are supported yet.

Also applies to: 175-188

internal/k8s_client/client.go (2)

162-212: ListResources extension to support fieldSelector looks correct; consider simplifying error wrapping

The extended ListResources correctly wires fieldSelector through logging, validation, and the client options, while preserving existing label selector behavior. One small polish opportunity: parseFieldSelector already returns a KubernetesError on malformed input, and that error is then wrapped again by errors.KubernetesError("invalid field selector %s: %v", fieldSelector, err). You could simplify by having parseFieldSelector return a plain error (e.g., via fmt.Errorf) and let ListResources be the single place that converts it into a KubernetesError, avoiding nested ServiceError messages.


214-263: Field selector parsing is straightforward; consider trimming whitespace for robustness

The parseFieldSelector/splitSelectorPairs/splitKeyValue helpers correctly handle simple comma‑separated key=value pairs and will fail fast on malformed items, which is good. To make this a bit more forgiving for callers, you might consider trimming whitespace around keys and values (e.g., handling "metadata.name = my-pod" as well as "metadata.name=my-pod"), which can be done cheaply with strings.TrimSpace in splitKeyValue without changing the overall behavior.

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

139-147: Consider logging the terminate error in the deferred failure cleanup.

The Cleanup method (lines 47-50) logs termination errors, but this deferred cleanup silently discards them. For debugging purposes, consider logging the error here as well.

 	defer func() {
 		if !setupSuccess && result.Container != nil {
 			log.Infof("Setup failed, force terminating container...")
 			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 			defer cancel()
-			_ = result.Container.Terminate(ctx)
+			if err := result.Container.Terminate(ctx); err != nil {
+				log.Infof("Container termination during setup failure cleanup: %v", err)
+			}
 		}
 	}()
pkg/errors/api_error.go (1)

152-155: AsAPIError is redundant—consider removing or documenting the distinction.

AsAPIError directly calls IsAPIError and provides no additional functionality. While the comment says it's "for more idiomatic Go usage," both names are commonly used (errors.As pattern). Consider removing one to avoid confusion, or document why both exist.

test/integration/k8s_client/helper_testcontainers_k3s.go (1)

96-110: Good addition for cleanup safety, but double termination may occur.

The immediate cleanup registration is a good pattern for preventing container leaks when assertions fail. However, this introduces potential double termination:

  1. t.Cleanup (registered here) will run at test end
  2. Cleanup() method (line 166) is likely also called by tests

While Terminate is typically idempotent, consider setting k3sContainer = nil after termination in the Cleanup method, or document that callers should not call Cleanup() explicitly when using this setup function.

Also, the inner nil check on line 100 is redundant since the outer check on line 98 already ensures k3sContainer != nil.

 	// Register cleanup immediately after creation to prevent leaks if assertions fail
 	if k3sContainer != nil {
 		t.Cleanup(func() {
-			if k3sContainer != nil {
-				ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-				defer cancel()
-				_ = k3sContainer.Terminate(ctx)
-			}
+			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+			defer cancel()
+			_ = k3sContainer.Terminate(ctx)
 		})
 	}
internal/criteria/README.md (1)

280-288: Potential type mismatch in integration example.

The example directly assigns cond.Operator to criteria.ConditionDef.Operator. Based on the relevant code snippets, criteria.Operator is a typed string (type Operator string), while config_loader.Condition.Operator is likely a plain string. This should work due to Go's type conversion rules, but for clarity, consider showing the explicit conversion:

Operator: criteria.Operator(cond.Operator),

This makes the type conversion explicit and helps readers understand the integration pattern.

internal/criteria/evaluator_test.go (1)

456-460: Consider adding test for zero value with exists operator.

The TestEvaluateExists test covers empty string, nil, empty slice, and empty map, but doesn't test zero numeric value (e.g., 0 or 0.0). Given that 0 is returned as true (line 457-459), you might want to add explicit documentation or a test case that clarifies this behavior, since some users might expect 0 to be treated as "not existing."

{
    name:  "zero value exists",
    field: 0,
    want:  true,  // Note: zero is considered to exist, unlike empty string
},
internal/hyperfleet_api/client.go (1)

110-115: Potential timeout conflict between HTTP client and per-request context.

When a custom http.Client is provided via WithHTTPClient, its Timeout may conflict with the per-request context timeout created in doRequest (line 230). The HTTP client's timeout applies globally, while the context timeout applies per-request.

If a custom client has a shorter timeout than the config timeout, requests may fail unexpectedly. Consider documenting this behavior or clearing the HTTP client's timeout when using per-request timeouts.

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

27-29: Redundant WithBaseDir option.

According to internal/config_loader/loader.go, the Load function automatically sets the base directory from the config file path. The explicit WithBaseDir here is redundant since it passes the same directory that Load would derive.

You can simplify to:

-	baseDir := filepath.Dir(configPath)
-	config, err := config_loader.Load(configPath, config_loader.WithBaseDir(baseDir))
+	config, err := config_loader.Load(configPath)

16-19: Consider handling runtime.Caller failure.

The ok return value from runtime.Caller(0) is discarded. If the call fails (unlikely but possible), filename will be empty, causing getProjectRoot to return an incorrect path.

 func getProjectRoot() string {
-	_, filename, _, _ := runtime.Caller(0)
+	_, filename, _, ok := runtime.Caller(0)
+	if !ok {
+		panic("failed to get current file path")
+	}
 	return filepath.Join(filepath.Dir(filename), "../../..")
 }
internal/config_loader/loader.go (1)

151-198: Validation pipeline ordering is reasonable and extensible

Running structural validators first, then optional adapter-version check, then file-reference validation, and finally semantic validation is a sensible progression. Early return on first failure keeps the surface simple while still allowing semantic validation to aggregate its own errors. If you later need to surface multiple structural issues at once, consider switching coreValidators to accumulate into a shared ValidationErrors, but it’s fine as-is.

internal/config_loader/validator.go (1)

150-225: Template variable validation covers key surfaces and uses a sensible heuristic

Walking preconditions, resources (including discovery selectors), post actions, and both top‑level and post params ensures most template surfaces are checked. The regex limiting to {{ .var }}‑style placeholders (and ignoring things like {{ now | date ... }}) is a pragmatic compromise that avoids false positives on functions while catching the common variable cases. Just be aware that more advanced template patterns (e.g., index on label keys with - or /) won’t be validated here, which seems acceptable given current usage.

internal/criteria/evaluator.go (1)

110-163: Wrap operator evaluation errors to include field context

Right now, errors returned from helpers like evaluateIn / evaluateContains are propagated directly, so callers lose the association with the field/operator that failed, while field‑lookup failures are wrapped in EvaluationError. You can make error handling more consistent (and debugging easier) by wrapping evaluation errors in EvaluationError as well.

 func (e *Evaluator) EvaluateConditionWithResult(field string, operator Operator, value interface{}) (*EvaluationResult, error) {
@@
 	case OperatorExists:
 		matched = evaluateExists(fieldValue)
 	default:
 		return nil, &EvaluationError{
 			Field:   field,
 			Message: fmt.Sprintf("unsupported operator: %s", operator),
 		}
 	}
 
 	if err != nil {
-		return nil, err
+		return nil, &EvaluationError{
+			Field:   field,
+			Message: fmt.Sprintf("operator %q evaluation failed", operator),
+			Err:     err,
+		}
 	}
 
 	result.Matched = matched
 	return result, nil
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d98944f and cfcabf5.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (41)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (2 hunks)
  • internal/k8s_client/client.go (2 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (3 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (4 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/k8s_client/client_test.go
🚧 Files skipped from review as they are similar to previous changes (10)
  • configs/templates/cluster-status-payload.yaml
  • configs/README.md
  • go.mod
  • configs/templates/deployment.yaml
  • internal/config_loader/validator_test.go
  • configs/templates/job.yaml
  • test/testdata/templates/deployment.yaml
  • internal/hyperfleet_api/types.go
  • internal/criteria/types.go
  • internal/config_loader/README.md
🧰 Additional context used
🧬 Code graph analysis (13)
internal/k8s_client/client.go (1)
pkg/errors/error.go (1)
  • KubernetesError (203-205)
test/integration/broker_consumer/adapter_integration_test.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (65-69)
internal/criteria/evaluator_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
  • EvaluationError (100-104)
internal/criteria/evaluator.go (5)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
  • EvaluationResult (12-23)
  • IsFieldNotFound (579-582)
  • FieldNotFoundError (567-571)
internal/hyperfleet_api/client.go (6)
internal/hyperfleet_api/types.go (9)
  • Client (213-231)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
internal/k8s_client/client.go (3)
  • Client (20-23)
  • ClientConfig (26-35)
  • NewClient (58-101)
pkg/errors/error.go (1)
  • New (107-123)
pkg/errors/api_error.go (1)
  • NewAPIError (123-134)
internal/config_loader/types.go (1)
  • Header (95-98)
internal/broker_consumer/consumer.go (1)
  • Close (134-143)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (84-92)
  • Resource (114-121)
  • SelectorConfig (131-134)
  • Parameter (47-63)
  • FetchExternalResource (66-71)
  • Precondition (74-81)
internal/config_loader/loader.go (4)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
  • ValidHTTPMethodsList (39-39)
test/integration/config-loader/loader_template_test.go (3)
internal/config_loader/loader.go (2)
  • Load (88-111)
  • WithBaseDir (69-73)
internal/config_loader/types.go (3)
  • Metadata (12-16)
  • APICall (84-92)
  • ExtractField (101-104)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (2)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
internal/criteria/evaluator.go (2)
  • ConditionDef (291-295)
  • NewEvaluator (48-55)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (474-486)
internal/config_loader/validator.go (1)
  • Validate (500-502)
cmd/adapter/main.go (5)
internal/config_loader/loader.go (3)
  • EnvConfigPath (22-22)
  • Load (88-111)
  • WithAdapterVersion (55-59)
internal/config_loader/types.go (2)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
internal/hyperfleet_api/client.go (3)
  • BaseURLFromEnv (121-123)
  • EnvBaseURL (21-21)
  • NewClient (100-118)
internal/hyperfleet_api/types.go (1)
  • Client (213-231)
internal/k8s_client/client.go (2)
  • Client (20-23)
  • NewClient (58-101)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (114-121)
  • Precondition (74-81)
  • PostAction (143-147)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/types.go (1)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/criteria/evaluator.go (1)
  • ConditionDef (291-295)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
🔇 Additional comments (83)
OWNERS (1)

2-2: LGTM: Quoting the numeric ID is good practice.

Quoting the numeric identifier ensures YAML treats it as a string rather than a number, which is the correct type for GitHub user IDs.

configs/broker-configmap-pubsub-template.yaml (2)

24-28: LGTM! Clearer naming convention for broker configuration keys.

The BROKER_ prefix provides better clarity about the purpose of these configuration values and avoids potential conflicts with other environment variables.


93-97: Example deployment correctly updated for BROKER_SUBSCRIPTION_ID.

The example deployment references the renamed key. Note that BROKER_TOPIC isn't consumed in the example deployment section—verify if it's needed by the adapter at runtime or if this is intentional.

test/integration/broker_consumer/adapter_integration_test.go (2)

91-91: LGTM! Reasonable timeout increase for integration tests.

Doubling the timeout from 30s to 60s accounts for variability in container startup and Pub/Sub emulator readiness.


118-152: Good improvement: Polling approach replaces fragile sleep-based synchronization.

The publishAndWaitForMessage helper elegantly handles the race condition where the subscriber may not be fully ready after Subscribe() returns. The ticker-based retry with context-driven termination is a solid pattern.

One consideration: this approach may publish multiple messages if the subscriber is slow to initialize, but that's acceptable for a smoke test verifying the adapter wrapper functionality.

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

40-52: LGTM! Proper cleanup implementation with timeout and error logging.

The cleanup method now properly terminates the container with a reasonable 30-second timeout and logs errors without failing the test (since the container may already be stopped).

pkg/errors/api_error.go (3)

16-45: Well-designed API error type with comprehensive context.

The APIError struct captures all relevant information for debugging API failures, including retry attempts and duration. The Error() method provides clear, actionable error messages.


56-59: Consider whether context.Canceled should be included in timeout detection.

IsTimeout() checks for HTTP 408 and context.DeadlineExceeded. Depending on usage patterns, you may also want to detect context.Canceled since it can occur when a parent context times out. However, if the distinction between cancellation and timeout is important to callers, the current implementation is correct.


61-99: LGTM! Comprehensive status code helpers.

The helper methods cover the most common HTTP status scenarios and follow a consistent pattern. This allows callers to handle specific error cases without hardcoding status codes.

internal/criteria/cel_evaluator.go (6)

326-328: Good fix: Using strconv.Quote for proper string escaping.

This correctly handles all escape sequences (backslashes, quotes, newlines, tabs, etc.) when formatting string values as CEL literals.


307-314: Design decision documented: exists operator for nested paths defers error handling to caller.

The comment at lines 283-285 clarifies that null-safety guards are intentionally omitted, with errors captured by EvaluateSafe(). This is a reasonable approach that lets callers decide how to handle missing fields.

For users expecting null-safe evaluation, consider adding a comment in the exists case explaining that has(a.b.c) will error if intermediate paths are missing.


76-77: Optional types enabled but optional chaining syntax not utilized.

cel.OptionalTypes() is enabled, which allows syntax like a.?b.?c for null-safe field access. However, the exists operator (lines 307-314) generates has(...) instead of using optional chaining. This is fine as a design choice, but if null-safe evaluation becomes needed in the future, the infrastructure is already in place.


51-69: LGTM! Proper nil handling and environment setup.

The constructor handles nil context gracefully by creating an empty one, and builds the CEL environment with dynamically registered variables from the context data.


131-209: Solid implementation of error-tolerant CEL evaluation.

EvaluateSafe provides a clean pattern for capturing evaluation errors without throwing, enabling callers to handle failures gracefully. The error categorization and logging at debug level is appropriate for production use.


268-269: I'll help you verify this review comment about the isEmptyValue function's handling of boolean false values. Let me start by examining the code context and control flow.
<function_calls>

#!/bin/bash

First, let's understand the file structure and locate the relevant function

wc -l internal/criteria/cel_evaluator.go


</function_calls>
<function_calls>

#!/bin/bash

Read the cel_evaluator.go file to understand context around lines 201-269

sed -n '195,280p' internal/criteria/cel_evaluator.go


</function_calls>
<function_calls>

#!/bin/bash

Let's also search for the isEmptyValue function definition to see the full implementation

rg -n "func.*isEmptyValue" --type=go internal/criteria/


</function_calls>
<function_calls>

#!/bin/bash

Get the full isEmptyValue function

rg -n -A 50 "func.*isEmptyValue" --type=go internal/criteria/cel_evaluator.go


</function_calls>
<function_calls>

#!/bin/bash

Search for tests related to isEmptyValue

find . -name "test.go" -type f | xargs rg -l "isEmptyValue|Matched.*false|types.Bool" --type=go


</function_calls>

Let me gather this information by examining the actual code file directly:
<function_calls>

#!/bin/bash

Get a larger context around the function to understand the complete flow

head -300 internal/criteria/cel_evaluator.go | tail -100


</function_calls>

internal/hyperfleet_api/README.md (1)

1-178: Documentation is comprehensive and well-structured.

The README provides clear documentation for the HyperFleet API client with good coverage of:

  • Client and request options
  • Backoff strategies with jitter explanation
  • Retryable status codes (correctly documenting all 5xx plus 408/429)
  • Integration patterns with adapter config
configs/adapter-config-template.yaml (1)

123-128: LGTM - new imageTag parameter follows established patterns.

The imageTag parameter is well-defined with appropriate source, type, default value, and description. Marking it as required: false is correct since it has a default value.

internal/criteria/evaluator_test.go (1)

1-9: Comprehensive and well-structured test suite.

The test file provides excellent coverage of the criteria evaluation engine with:

  • Table-driven tests for all operators
  • Null/nil handling edge cases
  • Safe access pattern verification
  • Result structure validation
  • Error type testing

The consistent use of require for setup assertions and assert for test assertions follows Go testing best practices.

test/integration/testutil/container.go (3)

157-170: LGTM! Good defensive cleanup on failed container starts.

The addition of container termination before retry when a container was created but failed to start (e.g., wait strategy timeout) prevents container leaks. The fallback to forceCleanupContainer when graceful termination fails is a good safety net.


237-265: Previous feedback addressed - simplified error logging.

The force cleanup now logs all errors without fragile string matching for "No such container", which addresses the past review concern. The approach of accepting some log noise in cleanup paths is reasonable for test utilities.


277-304: The ancestor filter IS supported by Podman and the code should work correctly.

According to official Podman documentation, podman ps supports the --filter ancestor=<image> flag just like Docker. This filter matches containers created from a given image across both tools, so the cleanup function should successfully remove leaked containers on Podman environments without any special handling required.

internal/hyperfleet_api/client.go (3)

149-152: LGTM! Retry normalization addresses previous feedback.

The normalization to ensure at least 1 attempt prevents the loop from being skipped entirely when retryAttempts <= 0, which was flagged in a previous review.


306-308: LGTM! Concurrency-safe jitter calculation.

Using package-level rand.Float64() instead of a per-client *rand.Rand instance addresses the data race concern from a previous review. The package-level function uses a locked source that's safe for concurrent use.


286-317: LGTM! Well-structured backoff calculation.

The backoff implementation correctly supports exponential, linear, and constant strategies with appropriate jitter (±10%) and max delay capping. The default case falls back to base delay, which is reasonable.

test/integration/config-loader/loader_template_test.go (1)

21-106: LGTM! Comprehensive template config validation.

The test thoroughly validates the shipped configuration template, checking API version, kind, metadata, adapter info, HyperFleet API settings, params, preconditions (including extraction and conditions), resources, and post-processing. This provides good regression protection for the config structure.

internal/hyperfleet_api/client_test.go (4)

1-93: LGTM! Good coverage of client construction options.

The tests comprehensively cover all ClientOption variants including timeout, retry attempts, backoff strategies (exponential, linear, constant), delays, default headers, and custom config. This ensures the functional options pattern works correctly.


194-232: LGTM! Well-designed retry test.

The test correctly validates retry behavior with atomic counters for thread-safe request counting. It verifies both the final response status and the number of attempts made, ensuring the retry logic works as expected.


378-420: LGTM! Backoff tests account for jitter.

Using ±20% tolerance (80ms-120ms for 100ms base) appropriately accounts for the ±10% jitter in the backoff calculation. The tests cover all three strategies and verify the expected behavior at different attempt numbers.


598-642: LGTM! APIError integration test validates end-to-end flow.

The test verifies that when retries are exhausted, the returned error is properly typed as APIError with correct method, status code, attempt count, and response body. This ensures proper error propagation for downstream handling.

cmd/adapter/main.go (3)

62-91: LGTM! Well-structured startup flow with proper error handling.

The configuration loading sequence is clear: load config → validate API base URL → create API client. Error messages are descriptive and include relevant context. The log messages at each step provide good observability.


138-163: Good documentation for future implementation.

The TODO comments clearly document the expected event processing flow and emphasize the importance of using the per-message context (ctx) for API calls. The explicit references to adapterConfig and apiClient with the underscore pattern is a pragmatic way to avoid unused variable warnings until the implementation is complete.


188-218: LGTM! Defensive validation for backoff strategy.

The createAPIClient function properly validates the backoff strategy against known values (exponential, linear, constant) and returns a clear error for invalid values. The timeout parsing uses the accessor method which centralizes duration parsing logic.

One observation: when RetryAttempts is 0, it's not set as an option, so the client defaults apply. This is consistent with the client's normalization that ensures at least 1 attempt.

internal/criteria/evaluator_scenarios_test.go (5)

1-9: LGTM!

Clean package declaration and imports. Using testify's assert and require packages is appropriate for this test file.


10-89: LGTM!

TestRealWorldScenario provides good coverage of realistic adapter config scenarios. The test structure with subtests is clean, and the simulated cluster details data structure is comprehensive.


131-153: Good practice: isolated context for subtests.

Creating a local context and evaluator for the "replicas equal ready replicas" subtest (lines 133-136) prevents shared state issues. This is a good pattern.


192-251: LGTM!

TestMapKeyContainment provides comprehensive coverage for the Contains operator with both map[string]interface{} and map[string]string types. Good coverage of found/not-found scenarios.


284-363: LGTM!

TestNodeCountValidation uses idiomatic table-driven tests with proper test isolation by creating a new context and evaluator per subtest (lines 333-338). Good boundary testing with min/max edge cases.

internal/config_loader/loader_test.go (8)

1-11: LGTM!

Clean package declaration and minimal imports (os, path/filepath, testing, testify). Good use of standard library for file operations.


12-71: LGTM!

TestLoad provides comprehensive end-to-end loading test with a realistic YAML config. Good use of t.TempDir() for automatic cleanup.


80-174: LGTM!

TestParse uses table-driven tests effectively to cover valid configs and various validation error scenarios (missing apiVersion, kind, metadata.name, adapter.version).


491-523: LGTM!

TestGetRequiredParams validates the filtering logic correctly, ensuring only required params are returned in order.


761-889: LGTM!

TestValidateFileReferences has thorough coverage for buildRef validation including: valid refs, non-existent files, directory refs, manifest.ref, and post.params. Good use of temp files for realistic testing.


985-1066: LGTM!

TestLoadFileReferencesContent validates that file reference content is properly loaded and parsed. Good verification that the original ref key is replaced with actual content and BuildRefContent is populated correctly.


1068-1263: LGTM!

TestValidateResourceDiscovery provides comprehensive validation coverage for discovery configuration requirements including namespace, byName/bySelectors, and selector content validation.


1265-1335: LGTM!

TestLoadManifestRefsArray validates the multi-manifest loading feature, verifying that both items are loaded into ManifestItems with correct content.

internal/config_loader/validator_schema.go (9)

16-29: LGTM!

Clean SchemaValidator struct design with config and baseDir fields. The constructor NewSchemaValidator is straightforward.


31-51: LGTM!

ValidateStructure uses a clean validator chain pattern with fail-fast behavior. The ordered validator list ensures validations run in a logical sequence.


75-104: LGTM!

Core validators (validateAPIVersionAndKind, validateMetadata, validateAdapterSpec) provide clear error messages with proper field path context.


164-205: LGTM!

validateResourceDiscovery and validateSelectors enforce the required discovery configuration with clear error messages. Good validation that at least one selector type is defined when using bySelectors.


252-297: LGTM!

validateFileReferences collects all file reference errors before returning, providing a comprehensive error report. The error formatting with bullet points is user-friendly.


299-324: LGTM!

validateFileExists properly checks for empty paths, non-existent files, and directories. Error messages include both the original ref path and the resolved path for debugging.


410-440: Path traversal protection correctly implemented.

The resolvePath function now returns an error for paths that escape the base directory by using filepath.Rel and checking for ".." prefix. This addresses the previous security concern.


391-408: LGTM!

loadYAMLFile properly handles file reading and YAML parsing with clear error messages including the full path.


488-526: LGTM!

Legacy wrapper functions maintain backward compatibility while delegating to the new SchemaValidator. This enables gradual migration without breaking existing code.

internal/config_loader/accessors.go (7)

12-18: LGTM!

BuiltinVariables provides a clear list of always-available template/CEL variables. Good documentation.


31-79: LGTM!

GetDefinedVariables comprehensively collects all variable sources. Good nil check at the start and proper handling of optional sections like Post.


82-132: LGTM!

Name-based lookup methods (GetParamByName, GetResourceByName, GetPreconditionByName) follow a consistent pattern with nil-safety and return pointers to slice elements correctly using index-based iteration.


134-145: LGTM!

GetPostActionByName correctly handles the additional nil check for c.Spec.Post before accessing PostActions.


175-182: Consider documenting the zero-value behavior.

ParseTimeout returns (0, nil) when timeout is empty. This is a valid design, but callers must know to check for zero and apply a default. The comment on line 176 mentions this, which is good.


188-229: LGTM!

HasManifestRef and GetManifestRefs correctly handle both single ref and array refs patterns. Good use of the normalizeToStringKeyMap helper for robustness.


252-274: LGTM!

normalizeToStringKeyMap handles both map[string]interface{} (yaml.v3) and map[interface{}]interface{} (yaml.v2) for robustness across YAML parser versions. Non-string keys are safely converted using fmt.Sprintf.

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

1-17: LGTM!

Build tag integration is correctly specified. Imports are appropriate for integration testing with config_loader and criteria packages.


19-30: LGTM!

getConfigPath provides flexible path resolution with environment variable override and fallback to runtime-based path resolution. Good for CI/local development flexibility.


32-113: LGTM!

TestConfigLoadAndCriteriaEvaluation provides comprehensive end-to-end testing of config loading and precondition evaluation, including per-condition and combined evaluation with result verification.


115-170: LGTM!

TestConfigConditionsToCEL validates the CEL conversion and evaluation pathway, ensuring conditions from config can be translated to CEL expressions and evaluated correctly.


172-238: LGTM!

TestConfigWithFailingPreconditions provides good coverage of failure scenarios with wrong phase, wrong provider, and missing vpcId. Tests correctly verify that conditions fail as expected.


266-359: LGTM!

TestConfigPostProcessingEvaluation simulates realistic K8s resource data and validates nested field access, condition evaluation, and CEL expression evaluation against the simulated data.


361-417: LGTM!

TestConfigNullSafetyWithMissingResources provides excellent coverage of null-safety scenarios including missing resources, nil resources, and deeply nested nulls. Tests verify safe accessors return appropriate defaults.


440-450: LGTM!

The parameter source validation now correctly uses strings.HasPrefix (line 445) to avoid the potential panic from the previous implementation. This addresses the earlier review concern.

internal/config_loader/loader.go (2)

48-73: Functional options setup and WithBaseDir precedence look solid

loaderConfig plus LoaderOption is used correctly, and prepending WithBaseDir while still allowing it to be overridden by later options gives callers flexibility without complicating the API. No changes needed here.


88-111: Load/Parse separation keeps concerns clean

Deriving baseDir once in Load and feeding it into Parse via options is a clean split between I/O and pure parsing/validation, and the error message when the path is missing is explicit about the env var. This is a good pattern and should make future extensions (e.g., alternative sources) straightforward.

internal/criteria/cel_evaluator_test.go (4)

22-113: Comprehensive coverage of core CEL evaluation behavior

The table in TestCELEvaluatorEvaluate plus the nested-data and EvaluateSafe tests cover happy paths, compound expressions, empty expressions, invalid syntax, and several error modes. This gives strong confidence in CELEvaluator’s behavior, especially around how Matched, Value, and Error are populated.


257-393: ConditionToCEL test matrix nicely pins down operator semantics

The cases here (including nested fields and the various exists forms) clearly define the expected CEL syntax for each operator and path shape. This should make future refactors to ConditionToCEL much safer, since any accidental change in mapping will be caught.


465-497: Good edge‑case coverage in TestFormatCELValue

Verifying quoting, escaping, numeric types (including time.Duration), slices, and several unsupported kinds gives solid guardrails around formatCELValue’s behavior. This is a nice balance of realism and safety checks.


499-572: Nice focused scenarios for EvaluateSafe error handling

The dedicated TestEvaluateSafeErrorHandling subtests make the distinction between “expression succeeded but not matched” and “expression failed (field not found/missing intermediate)” very explicit, which is important for callers that branch on IsSuccess vs HasError. This structure will help keep regressions in error classification from slipping in.

internal/config_loader/validator.go (3)

18-42: Validation error aggregation is well‑structured

ValidationError plus ValidationErrors.Error() gives concise, path‑annotated messages while preserving all individual failures. This will make debugging invalid configs much easier without forcing callers to dig through partial state.


303-342: CEL environment initialization aligns with dynamic config needs

Building the CEL env from definedParams roots with cel.DynType (and always adding a resources map) is a good way to keep validation flexible while still catching obvious parse errors. Skipping hard failures on Check and logging at V(2) is also a nice trade‑off between developer feedback and not over‑constraining dynamic expressions.


431-491: Kubernetes manifest validation is minimal but effective

Checking for apiVersion, kind, metadata, and metadata.name (plus non‑empty apiVersion/kind) provides a solid baseline without trying to re‑implement full schema validation. Combined with envtest‑based integration tests, this seems like an appropriate level of static checking.

test/testdata/adapter_config_valid.yaml (1)

1-202: Config fixture is consistent with the new schema and validation rules

This “valid” adapter config lines up with the Go types and validator expectations: parameter names match the templates/CEL expressions, preconditions use supported operators, manifests include the required K8s fields, and post params/postActions exercise both template and CEL validation paths. It’s a good end‑to‑end example for the loader/validator pipeline.

internal/config_loader/types.go (1)

3-159: Schema types map cleanly to the YAML and validation logic

AdapterConfig, AdapterConfigSpec, HyperfleetAPIConfig, Parameter, Precondition, Resource, and Post* types all match the YAML keys in the fixtures and what validator.go expects to walk. Internal fields like BuildRefContent and ManifestItems being yaml:"-" is the right choice to keep loader state out of the public schema. Keeping Condition.Operator as a raw string in this package also avoids introducing a dependency on the criteria operator enum into the config schema, which seems like a reasonable layering decision.

internal/criteria/evaluator.go (3)

37-64: Evaluator construction and lazy CEL initialization look correct

NewEvaluator’s nil‑context handling plus the getCELEvaluator use of sync.Once give you safe reuse of the underlying CEL environment across calls and goroutines without oversynchronization. This is a solid foundation for the higher‑level helpers.


471-564: Field traversal helpers handle maps and structs defensively

getFieldRecursive + getFieldValue + getStructField cover map[string]interface{}, map[interface{}]interface{}, and structs (with case‑insensitive field lookup), and return FieldNotFoundError with a useful Path in all the expected failure modes (nil parents, wrong type, missing field). Together with IsFieldNotFound now using errors.As, this should give robust behavior for both direct evaluation and higher‑level helpers like EvaluateSafe in the CEL layer.


566-582: IsFieldNotFound fix correctly handles wrapped errors

Switching to errors.As means callers can reliably detect FieldNotFoundError even when it’s wrapped inside EvaluationError or other error layers. This aligns with how EvaluateConditionWithResult reports lookup failures and makes the error model much easier to consume.

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

🧹 Nitpick comments (11)
test/integration/testutil/container.go (2)

157-170: Consider using config.CleanupTimeout for consistency.

The termination logic correctly prevents container leaks when a start attempt fails. However, the hardcoded 30-second timeout (Line 161) is inconsistent with the 60-second minimum enforced in the main cleanup path (Lines 188-190).

Apply this diff to use the configured cleanup timeout:

-		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+		cleanupTimeout := config.CleanupTimeout
+		if cleanupTimeout < 60*time.Second {
+			cleanupTimeout = 60 * time.Second
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)

267-305: Consider using CombinedOutput() for better error visibility.

The leaked container cleanup logic is sound and provides useful test hygiene. However, Line 280 uses Output() which only captures stdout. If the listing command fails, stderr output won't be captured, making debugging harder.

Apply this diff for consistency with forceCleanupContainer:

-	output, err := listCmd.Output()
+	output, err := listCmd.CombinedOutput()

This captures both stdout and stderr, providing better diagnostic information when the command fails.

internal/k8s_client/discovery.go (1)

63-100: Consider safer handling for nil discovery and document return semantics

Right now, a nil discovery returns (nil, nil). If a caller forgets to guard for that and iterates list.Items, they’ll panic on a nil list. To reduce footguns, consider either:

  • returning an empty *unstructured.UnstructuredList instead of nil, or
  • explicitly documenting that nil indicates “no discovery configured” and must be checked by callers.

The rest of the flow (single-resource wrapping into a list, or delegating to ListResources for selector-based discovery) looks good.

internal/k8s_client/client_test.go (2)

9-43: Add a couple more assertions in the cluster-scoped discovery test

To fully exercise DiscoveryConfig in the cluster-scoped case, you might also assert GetName() is empty and GetLabelSelector() matches "type=cluster", mirroring the earlier subtests. This keeps all four methods covered consistently across scenarios.


45-89: Make multi-label expectations explicit in the table instead of branching on test name

The TestBuildLabelSelector logic is sound, but keying special behavior off tt.name == "multiple labels" is a bit brittle. Consider adding explicit expectation fields (e.g., contains []string / requireComma bool) or a small validate func per case so each row fully defines its own checks without relying on the string name.

internal/k8s_client/README.md (1)

184-225: Small consistency tweaks to the Discover/List examples (optional)

The longer “Discover Resources” and “List Resources (Low-Level)” sections look accurate and helpful. Two minor polish ideas:

  • In the second DiscoverResources example (single resource), also check and handle err as you do in the first example for consistency.
  • Optionally use a different variable name (e.g., singleDiscovery) for the second DiscoveryConfig to make it a bit clearer you’re switching modes in the same snippet.

These are purely cosmetic; the current docs are already serviceable.

test/integration/broker_consumer/adapter_integration_test.go (1)

166-171: Inconsistent indentation.

Lines 166-171 have mixed indentation (extra tab before // Setup test environment and defer cleanupEnv()). This appears to be a pre-existing formatting issue but should be cleaned up for consistency.

-		// Setup test environment
-	_, cleanupEnv := setupTestEnvironment(t, projectID, emulatorHost, "concurrent-test")
-		defer cleanupEnv()
+	// Setup test environment
+	_, cleanupEnv := setupTestEnvironment(t, projectID, emulatorHost, "concurrent-test")
+	defer cleanupEnv()
 
 	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
-		defer cancel()
+	defer cancel()
configs/templates/job.yaml (1)

20-20: Consider making the validator image tag configurable.

The validator image uses a hardcoded tag v1.0.0, while other templates (e.g., deployment.yaml) use a configurable {{ .imageTag }} variable. For consistency and easier version management, consider parameterizing this.

-          image: "quay.io/hyperfleet/validator:v1.0.0"
+          image: "quay.io/hyperfleet/validator:{{ .validatorImageTag | default \"v1.0.0\" }}"
pkg/errors/api_error.go (1)

136-155: IsAPIError and AsAPIError are identical.

Both functions perform the same operation. While AsAPIError is documented as an alias for idiomatic Go usage, having two functions that do exactly the same thing could cause confusion. Consider documenting which one is preferred or removing one.

If both are intentional for different calling conventions, consider updating the doc comment to clarify:

-// AsAPIError is an alias for IsAPIError for more idiomatic Go usage
+// AsAPIError is an alias for IsAPIError. Prefer AsAPIError for
+// consistency with the standard library's errors.As naming convention.
cmd/adapter/main.go (1)

157-159: Track removal of placeholder assignments.

The _ = adapterConfig and _ = apiClient assignments suppress unused variable warnings temporarily. Ensure these are removed when the actual event processing logic is implemented.

Would you like me to open an issue to track implementing the event processing logic outlined in the TODO comments (lines 145-155)?

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

135-147: Consider logging termination errors for debugging consistency.

The deferred cleanup correctly handles setup failures, but line 145 silently ignores the termination error (_ = result.Container.Terminate(ctx)), while the Cleanup method logs such errors (lines 48-49). For debugging failed setups, it would be helpful to know if container termination itself also failed.

Apply this diff to add consistent error logging:

 		if !setupSuccess && result.Container != nil {
 			log.Infof("Setup failed, force terminating container...")
 			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 			defer cancel()
-			_ = result.Container.Terminate(ctx)
+			if err := result.Container.Terminate(ctx); err != nil {
+				log.Infof("Force termination: %v", err)
+			}
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfcabf5 and b459ffe.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (41)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (2 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (4 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/hyperfleet_api/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • internal/criteria/README.md
  • configs/templates/deployment.yaml
  • configs/adapter-config-template.yaml
  • internal/hyperfleet_api/client.go
  • internal/k8s_client/client.go
  • configs/README.md
  • internal/config_loader/README.md
  • test/integration/k8s_client/helper_testcontainers_k3s.go
  • test/integration/config-loader/loader_template_test.go
  • test/testdata/templates/deployment.yaml
  • OWNERS
  • internal/hyperfleet_api/types.go
  • go.mod
🧰 Additional context used
🧬 Code graph analysis (10)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (7)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
internal/config_loader/validator_test.go (4)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/config_loader/types.go (3)
  • AdapterConfig (4-9)
  • Resource (114-121)
  • DiscoveryConfig (124-128)
internal/config_loader/validator.go (2)
  • Validate (496-498)
  • ValidationErrors (29-31)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/config_loader/loader_test.go (4)
internal/config_loader/loader.go (3)
  • Load (88-111)
  • Parse (114-130)
  • LoadWithVersion (134-136)
internal/config_loader/types.go (10)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
  • AdapterConfig (4-9)
  • AdapterConfigSpec (19-27)
  • AdapterInfo (30-32)
  • Parameter (47-63)
  • Resource (114-121)
  • PostConfig (136-139)
  • DiscoveryConfig (124-128)
  • SelectorConfig (131-133)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (471-483)
internal/k8s_client/discovery.go (2)
  • Discovery (13-28)
  • DiscoveryConfig (31-41)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (114-121)
  • Precondition (74-81)
  • PostAction (142-146)
  • HyperfleetAPIConfig (35-39)
internal/k8s_client/discovery.go (2)
internal/config_loader/types.go (1)
  • DiscoveryConfig (124-128)
internal/k8s_client/client.go (1)
  • Client (20-23)
test/integration/broker_consumer/adapter_integration_test.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (65-69)
internal/config_loader/validator.go (4)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (84-92)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/k8s_client/discovery.go (1)
  • Discovery (13-28)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (2)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
internal/criteria/evaluator.go (2)
  • ConditionDef (291-295)
  • NewEvaluator (48-55)
internal/config_loader/types.go (2)
internal/k8s_client/discovery.go (2)
  • Discovery (13-28)
  • DiscoveryConfig (31-41)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
🔇 Additional comments (51)
test/integration/testutil/container.go (4)

9-10: LGTM! Standard library imports for container cleanup.

The os/exec and strings imports are appropriate for the new CLI-based cleanup utilities.


39-40: LGTM! Clear documentation of cleanup timeout semantics.

The extended comment explains the 60-second minimum and graceful stop behavior, which helps users understand the cleanup guarantees.


179-205: LGTM! Robust cleanup with proper timeout enforcement and fallback.

The cleanup logic correctly:

  • Captures the container ID upfront for reliable cleanup
  • Enforces a minimum 60-second timeout for graceful termination
  • Falls back to force removal if graceful termination fails
  • Provides clear logging for debugging

237-265: LGTM! Simplified error handling addresses previous review feedback.

The force cleanup implementation now logs all errors without fragile string matching for "No such container" detection. This simpler approach is acceptable for cleanup paths where some logging noise is tolerable.

The function correctly:

  • Tries both docker and podman runtimes
  • Captures combined output for debugging
  • Provides manual cleanup instructions if both fail

Note: This addresses the previous review comment about fragile string matching by removing the conditional logic and logging all errors consistently.

internal/k8s_client/discovery.go (2)

11-61: Discovery interface and DiscoveryConfig implementation are clear and cohesive

The interface cleanly captures the needed discovery knobs, and the default DiscoveryConfig plus its methods behave as expected (namespace/name/selector + IsSingleResource based solely on ByName). Pointer receivers and default-zero semantics also look appropriate for how this will be used.


102-114: BuildLabelSelector helper is correct and aligns with Kubernetes label selector syntax

Handling of nil/empty maps and building k=v comma-separated pairs is correct, and the function keeps responsibilities focused and small. Given that label map iteration order is undefined, the tests’ order-insensitive assertions are the right approach.

internal/k8s_client/README.md (1)

38-82: New DiscoverResources documentation and examples read well

The addition of DiscoverResources() to the architecture diagram and the short example under “Dynamic Client Operations” accurately reflects the new API surface and usage with DiscoveryConfig. The relationship between ListResources and DiscoverResources is clear from these snippets.

test/integration/broker_consumer/adapter_integration_test.go (2)

91-91: LGTM on timeout increase.

The 60-second timeout provides adequate buffer for container startup and message propagation in integration tests.


118-152: Well-designed polling mechanism.

The publishAndWaitForMessage helper elegantly replaces the previous fixed sleep with active polling. The ticker-based retry loop with context-driven termination properly handles the subscriber readiness race condition without blocking indefinitely.

configs/templates/cluster-status-payload.yaml (1)

1-16: Well-structured status payload template.

The template provides sensible defaults for all condition fields and uses RFC3339 format for timestamps. The structure aligns well with Kubernetes-style status reporting conventions.

configs/templates/job.yaml (1)

8-12: Labels issue addressed.

The required discovery labels (hyperfleet.io/resource-type and hyperfleet.io/managed-by) are now present.

internal/criteria/evaluator_test.go (3)

1-170: Comprehensive test coverage for evaluator core functionality.

The tests thoroughly cover the evaluation context lifecycle, basic operators (equals, in), and proper error handling. Table-driven test patterns are well-utilized.


272-412: Good boundary testing for numeric comparisons.

The TestEvaluateGreaterThan and TestEvaluateLessThan tests properly cover edge cases including zero values against positive, negative, and zero thresholds, as well as equal value scenarios.


959-1047: Excellent null-safety test coverage.

The TestNullHandling and TestDeepNullPath tests thoroughly verify graceful degradation when traversing null paths, ensuring the evaluator doesn't panic and provides appropriate defaults or errors.

internal/criteria/evaluator_scenarios_test.go (3)

10-89: Well-designed real-world scenario tests.

The TestRealWorldScenario effectively mirrors the adapter config template precondition checks, testing both individual conditions and combined evaluation. This provides valuable integration-level validation.


131-153: Good practice: isolated context per subtest.

Using a local context and evaluator for the "replicas equal ready replicas" subtest prevents shared state mutation issues. This pattern should be followed consistently across similar tests.


284-363: Thorough boundary testing for node count validation.

The parameterized tests effectively cover minimum, maximum, below-minimum, above-maximum, and at-boundary cases. Creating isolated context/evaluator per subtest ensures test independence.

pkg/errors/api_error.go (2)

14-50: Well-designed API error type.

The APIError struct captures comprehensive context for debugging API failures, including method, URL, status, response body, retry attempts, and duration. The Error() method provides clear, actionable messages with different formats for HTTP errors vs connection failures.


56-99: Comprehensive status code helpers.

The status helpers cover common HTTP error scenarios. IsTimeout properly checks both HTTP 408 and context.DeadlineExceeded, which is important for client-side timeout detection.

internal/criteria/types.go (3)

7-58: Clean operator type definitions.

The Operator type with well-documented constants and utility functions (IsValidOperator, OperatorStrings) provides a solid foundation for the criteria evaluation system. The SupportedOperators slice enables easy validation and iteration.


60-97: Well-designed EvaluationContext.

The context properly initializes the Data map in the constructor, handles nil in Merge, and provides clean accessors. The GetNestedField delegation to a helper keeps the API surface clean while allowing complex path resolution.


99-115: Proper error implementation.

EvaluationError correctly implements both error and the unwrap interface for errors.Is/errors.As support. The Error() method provides clear context with or without a wrapped error.

internal/hyperfleet_api/client_test.go (5)

18-93: Thorough client construction tests.

The tests cover various client option combinations including timeout, retry attempts, backoff strategies, and custom configs. This ensures the option pattern works correctly.


194-267: Well-designed retry behavior tests.

TestClientRetry and TestClientRetryExhausted effectively verify that the client retries on 5xx errors and properly reports attempt counts. Using atomic.AddInt32 ensures thread-safe counting in the concurrent test server handler.


269-297: Important: 4xx non-retry verification.

TestClientNoRetryOn4xx correctly verifies that client errors (400) are not retried, which is crucial for avoiding wasteful retries on permanent failures like bad requests.


378-420: Backoff calculation tests with appropriate tolerance.

The tests use ±20% tolerance to account for jitter in backoff calculations, which is the correct approach for testing randomized delays. All three strategies (exponential, linear, constant) are verified.


598-642: Excellent APIError integration test.

TestAPIErrorInRetryExhausted verifies the complete error flow: the client returns an APIError with proper method, status code, attempts, and response body when retries are exhausted. This ensures rich error context is available for debugging.

configs/broker-configmap-pubsub-template.yaml (1)

24-28: LGTM! Consistent BROKER_ prefix for public data keys.

The key renaming from SUBSCRIPTION_ID/TOPIC to BROKER_SUBSCRIPTION_ID/BROKER_TOPIC improves clarity and avoids potential naming collisions with other configuration keys.

cmd/adapter/main.go (2)

62-91: LGTM! Well-structured configuration loading and validation flow.

The initialization sequence properly:

  1. Loads configuration with version validation
  2. Verifies API base URL is set via environment
  3. Creates the API client with configured options

Error handling with logging and error wrapping is appropriate.


188-218: LGTM! Clean API client factory function.

The function properly:

  • Uses the ParseTimeout() accessor for duration parsing
  • Validates backoff strategy against known values with a clear error message
  • Conditionally applies options based on configuration values

The explicit validation of backoff strategies (lines 209-214) prevents silent misconfiguration.

internal/config_loader/loader_test.go (4)

12-71: LGTM! Comprehensive config loading test.

Good use of t.TempDir() for automatic cleanup and proper assertion hierarchy with require for critical checks and assert for field verifications.


80-174: LGTM! Well-structured table-driven tests for validation.

The test cases cover all required field validation scenarios with clear naming and expected error messages.


1068-1263: LGTM! Thorough resource discovery validation tests.

Excellent coverage of discovery configuration validation including:

  • Both byName and bySelectors discovery methods
  • Inline manifests and manifest references
  • Required field validation (namespace, selectors)
  • Edge cases like empty selectors

1265-1334: LGTM! Good test for manifest.refs array loading.

The test properly verifies that multiple manifest references are loaded into ManifestItems with the correct structure and order.

internal/config_loader/validator_test.go (4)

12-120: LGTM! Good operator validation test coverage.

Tests cover valid operators, invalid operators, and missing operators with appropriate error message verification.


122-268: LGTM! Comprehensive template variable validation tests.

Good coverage of variable scoping including:

  • Direct parameter references
  • Undefined variable detection in different contexts (URLs, manifests)
  • Variables extracted from precondition responses

580-656: LGTM! Good ManifestItems validation tests.

Tests properly verify that each item in ManifestItems is validated for required Kubernetes fields, with clear error indexing.


670-680: LGTM!

Clean test for the ValidationErrors helper methods and error message formatting.

internal/config_loader/accessors.go (3)

12-79: LGTM! Comprehensive variable definition collection.

The GetDefinedVariables method properly collects variables from all sources:

  • Built-in variables
  • Parameters
  • Precondition extracts and stored responses
  • Post params
  • Resource aliases

Nil-safety is properly handled throughout.


81-145: LGTM! Consistent accessor pattern.

All lookup methods use index-based iteration (for i := range) to correctly return stable pointers and include proper nil-safety checks.


252-274: LGTM! Robust YAML map normalization.

The normalizeToStringKeyMap helper correctly handles both map[string]interface{} (yaml.v3) and map[interface{}]interface{} (yaml.v2) for cross-library compatibility. The non-string key fallback using fmt.Sprintf is a sensible defensive measure.

internal/config_loader/validator_schema.go (4)

16-51: LGTM! Clean validator pattern.

The fail-fast validation pipeline with ordered validators provides clear error messages and efficient validation.


164-202: LGTM! Clear discovery validation rules.

The validation enforces sensible requirements:

  • Discovery is required for resource lookup on subsequent messages
  • Namespace is required with helpful guidance for all-namespaces ("*")
  • At least one discovery method (byName or bySelectors) must be configured

Error messages are clear and actionable.


407-437: LGTM! Path traversal protection properly implemented.

The resolvePath function now correctly:

  1. Resolves both base and target paths to absolute, cleaned paths
  2. Uses filepath.Rel to compute the relationship
  3. Rejects any path that would escape the base directory (relative path starting with "..")

This addresses the security concern from the previous review.


485-523: LGTM! Clean backward compatibility layer.

The wrapper functions maintain API compatibility while delegating to the new SchemaValidator implementation. The per-call instantiation is acceptable given the stateless nature of validation.

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

19-30: LGTM! Flexible config path resolution.

Good pattern using environment variable override with fallback to relative path resolution. This supports both local development and CI/CD environments.


32-113: LGTM! Comprehensive integration test.

Excellent integration coverage testing:

  • Config template loading with base directory
  • Evaluation context setup with realistic data
  • Both individual and combined condition evaluation
  • Extracted field verification

440-449: LGTM! Safe parameter source validation.

The use of strings.HasPrefix for validating parameter sources is safe and clean, avoiding the potential panic from direct string slicing that was flagged in a previous review.


361-417: LGTM! Important null-safety tests.

These tests verify critical defensive behavior when resources are missing or have null nested fields. The coverage of GetFieldSafe, GetFieldOrDefault, HasField, and EvaluateConditionSafe ensures the criteria evaluation handles incomplete data gracefully.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

42-51: LGTM! Cleanup implementation is robust.

The Cleanup method now properly terminates the container with a 30-second timeout and logs errors without failing the test, which is appropriate for cleanup operations. The idempotent nature of Terminate ensures this is safe to call multiple times across different cleanup paths.


139-139: LGTM! setupSuccess flag pattern is correct and well-placed.

The flag is initialized to false and set to true only after all setup steps complete successfully (line 186). This ensures the deferred cleanup runs only when setup fails partway through, providing proper resource cleanup for failure cases while avoiding unnecessary cleanup for successful setups.

Also applies to: 186-186

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 (12)
internal/k8s_client/client.go (1)

180-189: Misleading variable name for selector type.

The variable labelMap on line 185 actually holds a labels.Selector (returned by metav1.LabelSelectorAsSelector), not a map. Consider renaming it to labelSel or selector for clarity.

Apply this diff to improve the variable naming:

-	labelMap, err := metav1.LabelSelectorAsSelector(selector)
+	labelSel, err := metav1.LabelSelectorAsSelector(selector)
 	if err != nil {
 		return nil, errors.KubernetesError("failed to convert label selector: %v", err)
 	}
-	opts = append(opts, client.MatchingLabelsSelector{Selector: labelMap})
+	opts = append(opts, client.MatchingLabelsSelector{Selector: labelSel})
internal/k8s_client/discovery.go (1)

101-113: Make label selector output deterministic by sorting keys.

Map iteration order is non-deterministic in Go, so BuildLabelSelector will produce inconsistent output strings for the same input across different calls (e.g., "app=myapp,env=prod" vs "env=prod,app=myapp"). While functionally equivalent for Kubernetes, this can cause issues with testing, caching, or log consistency.

Apply this diff to sort keys for consistent output:

+import (
+	"sort"
+)
+
 func BuildLabelSelector(labels map[string]string) string {
 	if len(labels) == 0 {
 		return ""
 	}
 
-	pairs := make([]string, 0, len(labels))
+	keys := make([]string, 0, len(labels))
 	for k, v := range labels {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	pairs := make([]string, 0, len(labels))
+	for _, k := range keys {
-		pairs = append(pairs, k+"="+v)
+		pairs = append(pairs, k+"="+labels[k])
 	}
 	return strings.Join(pairs, ",")
 }
internal/hyperfleet_api/client.go (1)

254-257: Consider documenting the default Content-Type behavior.

Lines 254-257 automatically set Content-Type to "application/json" for any request with a body when no Content-Type is explicitly provided. While this is a sensible default for a JSON API client and can be overridden by the caller, it might surprise users sending non-JSON bodies. Consider documenting this behavior prominently in the README or function comments.

configs/templates/job.yaml (1)

20-20: Consider using the imageTag parameter for consistency.

The image tag is hard-coded to v1.0.0, but the PR introduces an imageTag parameter (configs/adapter-config-template.yaml, lines 123-128) that's used in the deployment template. Using the parameter here would enable consistent image versioning across all resources.

Apply this diff:

-          image: "quay.io/hyperfleet/validator:v1.0.0"
+          image: "quay.io/hyperfleet/validator:{{ .imageTag }}"
internal/config_loader/accessors.go (1)

252-274: Consider documenting non-recursive normalization behavior.

The helper only normalizes the top-level map, not nested maps. This is sufficient for current use cases (checking ref/refs keys), but worth noting in the comment if nested map handling is expected in the future.

 // normalizeToStringKeyMap converts various map types to map[string]interface{}.
 // This handles both map[string]interface{} (from yaml.v3) and map[interface{}]interface{}
 // (from yaml.v2 or other sources) for robustness.
 // Returns nil if the input is not a map type.
+// Note: This function does not recursively normalize nested maps.
 func normalizeToStringKeyMap(v interface{}) map[string]interface{} {
internal/config_loader/validator.go (3)

150-221: Template variable regex may miss keys with hyphens or other characters

templateVarRegex currently only matches identifiers matching [a-zA-Z_][a-zA-Z0-9_\.]*, so template variables like {{ .event.resource-id }} or other keys containing - (common in JSON/YAML) will not be validated at all—they simply won’t be matched by the regex.

If you expect such keys in templates, consider broadening the allowed character class, e.g.:

-var templateVarRegex = regexp.MustCompile(`\{\{\s*\.([a-zA-Z_][a-zA-Z0-9_\.]*)\s*(?:\|[^}]*)?\}\}`)
+var templateVarRegex = regexp.MustCompile(`\{\{\s*\.([a-zA-Z_][a-zA-Z0-9_\.\-]*)\s*(?:\|[^}]*)?\}\}`)

This keeps behavior for existing configs but starts validating variables with - in their segments.


298-338: Type used for resources in CEL env may not match the intent

initCELEnv intends to “always add resources as a map” via:

if !addedRoots["resources"] {
    options = append(options, cel.Variable("resources", cel.MapType(cel.StringType, cel.DynType)))
}

However, when GetDefinedVariables() includes keys like resources.clusterNamespace, the loop above will already register a root variable resources as cel.DynType, causing addedRoots["resources"] to be true and skipping the MapType registration. That means resources ends up as DynType instead of the more precise map type.

Since type-check errors are logged and not surfaced to users, this isn’t a functional bug, but you might want to enforce the map type explicitly, e.g.:

for varName := range v.definedParams {
    root := varName
    if idx := strings.Index(varName, "."); idx > 0 {
        root = varName[:idx]
    }

-   if addedRoots[root] {
-       continue
-   }
-   addedRoots[root] = true
-
-   options = append(options, cel.Variable(root, cel.DynType))
+   if root == "resources" {
+       // Defer to the explicit map registration below
+       continue
+   }
+   if !addedRoots[root] {
+       addedRoots[root] = true
+       options = append(options, cel.Variable(root, cel.DynType))
+   }
}
if !addedRoots["resources"] {
    options = append(options, cel.Variable("resources", cel.MapType(cel.StringType, cel.DynType)))
    addedRoots["resources"] = true
}

This keeps the declared intent and may improve CEL static checking around resources.* usages.


427-487: Kubernetes manifest checks cover basics; consider tightening type validation

The manifest validation correctly enforces presence of apiVersion, kind, metadata, and metadata.name, and ensures apiVersion/kind are non-empty strings. One gap is when these required fields exist but have the wrong type (e.g., apiVersion: 123); they will satisfy the “present” check and silently skip the string assertion branch.

If you’re not already enforcing field types in validator_schema.go, you could add a small type guard here to surface clearer errors, e.g. treating non-string apiVersion/kind as invalid:

if apiVersion, ok := manifest["apiVersion"]; ok {
    s, ok := apiVersion.(string)
    if !ok || s == "" {
        v.errors.Add(path+".apiVersion", "apiVersion must be a non-empty string")
    }
}

Same idea for kind and metadata’s type. Not strictly required if schema validation already covers it, but it would make this validator more robust on its own.

internal/criteria/types.go (1)

60-97: Consider defensively initializing EvaluationContext.Data in Set

NewEvaluationContext() initializes Data, but if a caller creates an EvaluationContext via a zero-value struct literal, Data will be nil and Set() will panic on assignment.

You could make Set more robust by lazily initializing the map:

func (c *EvaluationContext) Set(key string, value interface{}) {
-    c.Data[key] = value
+    if c.Data == nil {
+        c.Data = make(map[string]interface{})
+    }
+    c.Data[key] = value
}

This keeps existing behavior for normal usage while making the type safer to use in tests or alternative construction paths.

internal/config_loader/types.go (1)

73-153: Condition / WhenExpr operator typing relies on separate validation

Here Condition.Operator is a plain string and WhenExpr.Conditions reuses Condition. Given that config_loader.Validator already validates operators using criteria.IsValidOperator, this is workable and keeps YAML unmarshaling simple.

If you ever want stronger compile‑time coupling to criteria.Operator, you could introduce a local type alias and a small custom unmarshal, but that’s not required for current usage.

internal/criteria/evaluator.go (2)

92-163: Check intended semantics for OperatorExists with missing fields

EvaluateConditionWithResult always calls GetNestedField first and treats any error (including FieldNotFoundError) as fatal, returning an EvaluationError before the OperatorExists branch is reached. As a result:

  • EvaluateConditionSafe(..., OperatorExists, ...) will return false for missing fields (by swallowing the error), which matches a typical “exists” interpretation.
  • EvaluateConditionsWithResult will instead bail out with an error as soon as it hits a missing field, even for OperatorExists, instead of treating the condition as simply “not matched”.

If your intent is “exists should never surface an error for missing fields, only false”, you might want to special‑case FieldNotFoundError when handling OperatorExists, e.g.:

fieldValue, err := e.context.GetNestedField(field)
if err != nil {
    if operator == OperatorExists && IsFieldNotFound(err) {
        return &EvaluationResult{
            Field:         field,
            FieldValue:    nil,
            Operator:      operator,
            ExpectedValue: value,
            Matched:       false,
        }, nil
    }
    return nil, &EvaluationError{Field: field, Message: "failed to retrieve field", Err: err}
}

Otherwise, the current behavior is consistent but asymmetric between the “safe” and detailed APIs, so it’s worth confirming which behavior you want.


471-564: Nested field traversal is robust; consider guarding against unexported struct fields

The getNestedField / getFieldRecursive / getFieldValue pipeline correctly handles:

  • map[string]interface{} and map[interface{}]interface{} (common for YAML/JSON),
  • nil parents (with a FieldNotFoundError),
  • and structs via getStructField, including case‑insensitive name matching.

One potential edge case is getStructField calling f.Interface() without checking f.CanInterface(). For unexported fields on structs from other packages, Interface() will panic. This is unlikely in normal usage (your criteria paths will usually target exported/JSON fields), but it’s easy to harden:

if !f.IsValid() {
    return nil, &FieldNotFoundError{Path: path, Field: field,
        Message: fmt.Sprintf("field '%s' not found in struct", path)}
}
+if !f.CanInterface() {
+    return nil, &FieldNotFoundError{Path: path, Field: field,
+        Message: fmt.Sprintf("field '%s' is not accessible (unexported)", path)}
+}
return f.Interface(), nil

This keeps behavior for exported fields while avoiding a potential panic if someone accidentally targets an unexported field.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b459ffe and 475a764.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (41)
  • OWNERS (1 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (5 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (2 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (4 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • internal/k8s_client/README.md
  • internal/k8s_client/client_test.go
  • configs/templates/cluster-status-payload.yaml
  • cmd/adapter/main.go
  • configs/broker-configmap-pubsub-template.yaml
  • test/testdata/templates/deployment.yaml
  • configs/README.md
🧰 Additional context used
🧬 Code graph analysis (15)
internal/k8s_client/client.go (2)
internal/hyperfleet_api/types.go (1)
  • Client (213-231)
pkg/errors/error.go (1)
  • KubernetesError (203-205)
test/integration/config-loader/loader_template_test.go (2)
internal/config_loader/loader.go (2)
  • Load (88-111)
  • WithBaseDir (69-73)
internal/config_loader/types.go (3)
  • Metadata (12-16)
  • APICall (84-92)
  • ExtractField (101-104)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
internal/config_loader/validator_schema.go (2)
internal/config_loader/types.go (8)
  • AdapterConfig (4-9)
  • Metadata (12-16)
  • APICall (84-92)
  • Resource (114-121)
  • SelectorConfig (131-133)
  • Parameter (47-63)
  • FetchExternalResource (66-71)
  • Precondition (74-81)
internal/config_loader/loader.go (4)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
  • ValidHTTPMethodsList (39-39)
internal/config_loader/validator_test.go (3)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/config_loader/validator.go (2)
  • Validate (496-498)
  • ValidationErrors (29-31)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/k8s_client/discovery.go (3)
internal/config_loader/types.go (1)
  • DiscoveryConfig (124-128)
internal/hyperfleet_api/types.go (1)
  • Client (213-231)
internal/k8s_client/client.go (1)
  • Client (20-23)
internal/criteria/evaluator_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
  • EvaluationError (100-104)
internal/criteria/evaluator.go (5)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
  • EvaluationResult (12-23)
  • IsFieldNotFound (579-582)
  • FieldNotFoundError (567-571)
internal/config_loader/accessors.go (1)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (114-121)
  • Precondition (74-81)
  • PostAction (142-146)
  • HyperfleetAPIConfig (35-39)
internal/hyperfleet_api/types.go (1)
internal/k8s_client/client.go (1)
  • ClientConfig (26-35)
internal/config_loader/validator.go (4)
internal/config_loader/types.go (2)
  • AdapterConfig (4-9)
  • APICall (84-92)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/k8s_client/discovery.go (1)
  • Discovery (13-28)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (471-483)
internal/config_loader/validator.go (1)
  • Validate (496-498)
internal/criteria/cel_evaluator.go (2)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/evaluator.go (1)
  • ConditionDef (291-295)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
internal/hyperfleet_api/client.go (4)
internal/hyperfleet_api/types.go (10)
  • Client (213-231)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (54-63)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
pkg/errors/api_error.go (1)
  • NewAPIError (123-134)
internal/config_loader/types.go (1)
  • Header (95-98)
internal/broker_consumer/consumer.go (1)
  • Close (134-143)
internal/config_loader/types.go (2)
internal/k8s_client/discovery.go (2)
  • Discovery (13-28)
  • DiscoveryConfig (31-41)
internal/criteria/types.go (1)
  • Operator (8-8)
🔇 Additional comments (94)
internal/k8s_client/client.go (1)

162-169: LGTM! Clear documentation enhancement.

The expanded doc comment provides helpful guidance on parameters and directs users to DiscoverResources() for more flexible discovery workflows. This improves the API's discoverability.

internal/k8s_client/discovery.go (2)

11-28: LGTM! Well-designed interface.

The Discovery interface is clean, extensible, and well-documented. It provides a clear contract for resource discovery strategies and supports both single-resource and selector-based discovery patterns.


63-99: LGTM! Solid implementation of discovery workflow.

The DiscoverResources method correctly handles both discovery modes (single resource by name and multi-resource by selector). The nil check and consistent list return type are good design choices.

OWNERS (1)

2-2: Use GitHub username instead of numeric user ID for consistency.

While quoting the value is correct for YAML, using a numeric GitHub user ID ("86254860") appears inconsistent with other approver entries which use readable usernames. This reduces maintainability and makes it harder to identify the approver at a glance.

Consider replacing the numeric ID with the corresponding GitHub username.

test/integration/broker_consumer/adapter_integration_test.go (3)

91-91: LGTM! Reasonable timeout increase for integration tests.

Increasing the timeout to 60 seconds is appropriate for integration tests that involve container initialization and emulator setup, especially given the large emulator images mentioned in the PR objectives.


118-124: Excellent improvement! Polling eliminates race conditions.

Replacing the fixed sleep with an active polling mechanism is a robust solution that properly handles the race condition where the subscriber may not be fully ready immediately after Subscribe() returns. The approach provides clear timeout messaging.


126-152: Consider adding error handling and observability.

The helper function is well-structured with proper resource cleanup and context handling. However, two improvements would enhance debuggability:

  1. Check publishTestMessages errors: If publishing fails silently, the test will timeout without indicating the root cause was a publish failure rather than a subscriber issue.
  2. Log retry attempts: Adding a counter for publish attempts would improve observability and help diagnose slow subscriber readiness.
pkg/errors/api_error.go (4)

14-33: LGTM! Well-structured error context.

The APIError struct captures comprehensive context for HTTP API failures, including request details, response information, and retry metadata. This will be valuable for debugging and error handling.


35-50: LGTM! Error interface implemented correctly.

The Error() method provides context-appropriate formatting, and Unwrap() enables proper error chain inspection with errors.Is/As.


56-99: LGTM! Comprehensive status helpers.

The status code helpers cover common HTTP error scenarios and correctly check both status codes and context errors (e.g., IsTimeout checking both 408 and context.DeadlineExceeded).


122-150: LGTM! Constructor and helper are well-implemented.

NewAPIError provides a clean constructor, and IsAPIError correctly uses errors.As to detect APIError instances even when wrapped. The documentation example is helpful.

internal/hyperfleet_api/types.go (3)

12-31: LGTM! Well-designed backoff configuration.

The BackoffStrategy type and default constants provide a clean, config-friendly API. The default values (10s timeout, 3 retries, exponential backoff) are sensible for typical HTTP API interactions.


69-149: LGTM! Flexible request configuration with functional options.

The Request struct uses pointer fields (RetryAttempts, RetryBackoff) to correctly distinguish between "not set" and "explicitly set to zero," enabling proper per-request overrides. The functional options pattern provides a clean, extensible API.


155-231: LGTM! Response helpers and Client interface are well-designed.

The Response struct includes useful metadata (Duration, Attempts) and provides semantic helper methods. IsRetryable correctly treats all 5xx, 408, and 429 as retryable, matching the documented behavior. The Client interface provides a clean abstraction for HTTP operations.

internal/hyperfleet_api/README.md (1)

1-178: LGTM! Comprehensive and accurate documentation.

The README provides excellent coverage of the client's features, usage patterns, and configuration options. The retryable status codes section (lines 153-163) now correctly documents that "all 5xx responses" are retried, matching the implementation. The examples are clear and practical, including integration with the adapter config.

internal/hyperfleet_api/client.go (6)

29-97: LGTM! Clean client design with functional options.

The httpClient struct and ClientOption functions follow Go best practices. The functional options pattern provides flexible, readable configuration. Good to see the previous race condition concern was addressed by removing the per-client rand field.


99-118: LGTM! NewClient constructor is well-structured.

The constructor correctly initializes defaults, applies options, and creates an http.Client if needed. This ensures the client is always in a valid state.


139-219: LGTM! Robust retry logic with proper error handling.

The Do() method implements comprehensive retry logic with several good practices:

  • Lines 149-152 normalize retryAttempts to minimum 1, preventing the zero-attempt scenario from the past review discussion
  • Context cancellation is checked before each attempt (line 165) and during backoff (line 194)
  • Retryable vs non-retryable responses are correctly distinguished (line 178)
  • Final APIError includes full request context for debugging

285-317: LGTM! Backoff calculation with jitter is correctly implemented.

The calculateBackoff() method correctly implements all three backoff strategies and adds ±10% jitter to prevent thundering herd. Using package-level rand.Float64() (line 308) ensures thread-safety, addressing the previous concurrency concern.


323-384: LGTM! Convenience methods provide clean HTTP verb APIs.

The Get, Post, Put, Patch, and Delete methods correctly delegate to Do() while providing method-specific signatures. The implementation correctly includes body parameters only for methods that typically send bodies (POST, PUT, PATCH).


120-132: LGTM! Clean environment variable helpers.

BaseURLFromEnv and APIVersionFromEnv provide simple, clear interfaces for reading configuration from the environment. The "v1" default for API version is sensible.

internal/hyperfleet_api/client_test.go (1)

1-642: LGTM! Comprehensive test coverage.

The test suite thoroughly exercises the HTTP client functionality:

  • Client construction with various option combinations
  • All HTTP methods (GET, POST, PUT, PATCH, DELETE)
  • Header merging (default + per-request)
  • Retry logic including exhaustion scenarios
  • No retry on 4xx errors
  • Timeout and context cancellation
  • Response helpers across status codes
  • Backoff calculations with jitter tolerance
  • APIError creation and helper methods

The tests use good practices including table-driven tests, httptest for test servers, and atomic counters for thread-safe request counting.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

42-51: LGTM! Cleanup improvements are well-implemented.

The updated cleanup logic properly handles container termination with a timeout and logs errors without failing the test. The idempotent nature of Container.Terminate makes this safe to call multiple times.


136-147: LGTM! Setup failure cleanup pattern is correct.

The deferred cleanup with setupSuccess flag ensures containers are terminated if setup fails, preventing resource leaks. The pattern correctly:

  • Registers cleanup before assertions (line 140)
  • Guards cleanup with both !setupSuccess and non-nil container checks
  • Sets setupSuccess = true only after successful setup (line 186)
go.mod (1)

7-15: LGTM! Dependency updates align with new features.

The addition of github.com/google/cel-go v0.26.1 and related dependencies (cel.dev/expr, antlr4-go) correctly supports the new CEL evaluation capabilities introduced in this PR. Past review discussions confirmed these are the latest available versions.

test/integration/k8s_client/helper_testcontainers_k3s.go (1)

96-106: LGTM! Cleanup registration prevents container leaks.

The immediate cleanup registration after container creation ensures the K3s container is terminated even if subsequent assertions fail. This aligns with the cleanup pattern used in helper_envtest_prebuilt.go and follows testcontainers best practices.

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

123-128: LGTM! imageTag parameter is well-defined.

The new imageTag parameter provides runtime configurability for container images with sensible defaults. The parameter definition correctly specifies:

  • Environment variable source (env.IMAGE_TAG)
  • String type with default "v1.0.0"
  • Optional (required: false) for backward compatibility
  • Clear description
internal/criteria/evaluator_scenarios_test.go (3)

10-89: LGTM! Comprehensive real-world scenario coverage.

The test correctly simulates a realistic adapter workflow with cluster details, parameter extraction, and precondition evaluation. The scenarios align well with the adapter config template structure and validate key operators (In, Exists, Equals) and combined condition evaluation.


132-153: LGTM! Proper test isolation prevents shared state mutation.

Creating an isolated context for the subtest (lines 132-136) correctly prevents shared state issues that could arise from concurrent or sequential test execution. This pattern ensures test reliability.


286-362: LGTM! Table-driven test with proper isolation.

The parameterized node count validation test correctly creates an isolated context per subtest (lines 333-337), enabling safe parallel execution. The test cases comprehensively cover boundary conditions (min, max, below, above, at boundaries).

internal/config_loader/validator_schema.go (3)

410-437: LGTM! Path traversal protection is correctly implemented.

The resolvePath function properly prevents directory traversal attacks by:

  • Converting both base and target paths to absolute, clean paths
  • Using filepath.Rel to compute the relative path from base to target
  • Rejecting any path where the relative path starts with ".." (lines 431-434)

This approach correctly addresses the security concern raised in the past review.


296-321: LGTM! File validation is comprehensive.

The validation correctly checks for:

  • Empty file references
  • File existence via os.Stat
  • File type (ensuring it's not a directory, line 316)

Error messages clearly indicate the issue and include both the original and resolved paths for debugging.


31-51: LGTM! Fail-fast validation pipeline is well-structured.

The ValidateStructure method uses a slice of validator functions and returns on the first error, providing a clean fail-fast validation pipeline. This approach is efficient and provides clear error messages to users.

test/testdata/adapter_config_valid.yaml (3)

24-51: LGTM! Parameter definitions align with new schema.

The parameters section correctly defines all required variables with proper types, sources, and defaults. The structure matches the updated config schema introduced in this PR.


52-89: LGTM! Preconditions demonstrate key validation patterns.

The preconditions correctly showcase:

  • API call with response extraction (clusterStatus)
  • Structured conditions with valid operators (in, exists)
  • CEL expression evaluation (validationCheck)

These patterns align with the real-world scenarios tested in the evaluator test suite.


106-134: LGTM! Discovery configurations are properly defined.

All resources include required discovery configurations with either byName or bySelectors. The namespace field is correctly set for both namespaced resources ("cluster-{{ .clusterId }}") and cluster-scoped resources ("*"), meeting the schema validation requirements.

configs/templates/deployment.yaml (1)

1-37: LGTM! Well-structured Kubernetes deployment template.

The deployment template follows best practices with proper labels, selectors, and resource constraints. The use of templated variables (.clusterId, .imageTag, .resourceId) aligns with the parameter definitions in the adapter configuration.

internal/criteria/README.md (1)

1-382: LGTM! Comprehensive and well-structured documentation.

The documentation effectively covers all aspects of the criteria evaluation engine, including operators, usage patterns, examples, and integration with the config loader. The examples are clear and practical, making it easy for users to understand how to use the package.

internal/config_loader/validator_test.go (1)

1-757: LGTM! Comprehensive and well-structured test suite.

The test suite provides excellent coverage of the validator functionality with clear, table-driven tests. The tests cover all major validation scenarios including operators, template variables, CEL expressions, Kubernetes manifests, and error handling. The use of both YAML-based parsing tests and direct struct construction (for ManifestItems) demonstrates thorough testing of different code paths.

test/integration/config-loader/loader_template_test.go (1)

1-132: LGTM! Well-designed integration tests.

The integration tests effectively validate config loading with real YAML files while using helpers like getProjectRoot() and findExtractByAs() to keep the tests maintainable. The tests strike a good balance between comprehensive validation and avoiding brittleness by checking key fields rather than every detail.

test/integration/testutil/container.go (4)

157-170: Approve enhanced retry cleanup logic.

The addition of cleanup logic for failed containers before retry attempts is a good improvement. This prevents container leaks when wait strategies timeout but the container was created. The force cleanup fallback (lines 165-167) provides additional resilience.


188-190: Approve minimum cleanup timeout enforcement.

Enforcing a minimum 60-second cleanup timeout is a sensible safeguard to ensure containers have adequate time to stop gracefully, preventing stuck containers and resource leaks.


237-265: Approve force cleanup implementation with improved error handling.

The force cleanup function provides a necessary fallback when testcontainers' Terminate() fails. The change from string-based error filtering (per past review) to logging all errors (line 259) is an improvement, accepting some "not found" noise as reasonable for cleanup paths.


267-305: Approve leaked container cleanup utility.

The CleanupLeakedContainers function is a valuable addition for cleaning up containers from failed test runs. It provides a practical solution for test environment hygiene, especially useful in CI environments where tests may be interrupted.

internal/criteria/evaluator_test.go (1)

1-1060: LGTM! Comprehensive test coverage with excellent null safety validation.

The test suite provides thorough coverage of the evaluator functionality, including all operators, context management, field access patterns, and error handling. The extensive null safety tests (lines 959-1047) are particularly valuable, ensuring the evaluator handles missing/null fields gracefully. The tests for different evaluation methods and field extraction variants demonstrate well-designed APIs with multiple usage patterns.

internal/config_loader/loader_test.go (1)

1-1335: LGTM! Comprehensive test suite with excellent coverage.

The test suite provides thorough coverage of the config loader functionality, including loading, parsing, validation, and file reference handling. The tests are well-organized with clear names and cover both success and failure cases. The use of temporary directories for file-based tests and specific error message assertions demonstrates good testing practices. The manifest.refs array tests (lines 1265-1334) and discovery validation tests (lines 1068-1263) are particularly thorough.

internal/config_loader/README.md (1)

290-290: Minor: Verify package path reference.

The reference to internal/k8s-client at line 290 may be inconsistent with the actual package path. Verify whether the package directory is named internal/k8s-client (with hyphen) or internal/k8s_client (with underscore) to ensure consistency with Go package naming conventions.

internal/config_loader/accessors.go (6)

1-18: LGTM: Well-organized package with clear built-in variables.

The BuiltinVariables() function provides a clean API for template/CEL variable discovery.


31-79: LGTM: Comprehensive variable aggregation with proper nil-safety.

The method correctly aggregates variables from all configuration sources (built-ins, params, precondition extracts, post params, and resource aliases) with appropriate nil checks.


82-169: LGTM: Consistent accessor pattern with correct pointer semantics.

The use of index-based iteration (for i := range) with &slice[i] correctly returns pointers to the actual slice elements rather than copies. The nil-receiver guards are consistent across all methods.


175-182: LGTM: Clean timeout parsing with nil-safe behavior.

The function correctly returns zero duration with nil error when timeout is empty, allowing callers to apply defaults. The documentation is clear about this behavior.


188-229: LGTM: Robust manifest reference handling.

The functions correctly handle both single ref and array refs patterns with proper empty string filtering and YAML map normalization.


231-246: LGTM: Clean manifest unmarshaling with informative error messages.

The error message includes the actual type (got %T) which aids debugging when manifest is not a map type.

internal/config_loader/loader.go (6)

15-39: LGTM: Well-defined constants and supported versions.

The constants and valid HTTP methods are clearly defined with both a map for O(1) lookup and a list for error messages.


45-73: LGTM: Clean functional options pattern.

The options pattern is correctly implemented. As discussed in previous review, the loaderConfig is always initialized before options are applied, so nil checks are unnecessary.


84-111: LGTM: Robust file loading with automatic base directory resolution.

The design allows automatic baseDir derivation from the config file path while still permitting user override via options. Error messages are informative.


113-130: LGTM: Clean parsing flow with proper error handling.

The function correctly applies options, unmarshals YAML, and runs the validation pipeline with appropriate error wrapping.


132-142: LGTM: Clear deprecation with migration guidance.

The deprecated wrappers provide backward compatibility while guiding users to the new functional options API.


148-198: LGTM: Well-structured validation pipeline with clear separation of concerns.

The pipeline correctly sequences validations: core structural → adapter version → file references → semantic. The conditional execution based on cfg options is clean.

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

19-30: LGTM: Robust config path resolution.

The getConfigPath() function correctly prioritizes environment variable over fallback path resolution using runtime.Caller.


32-113: LGTM: Comprehensive happy-path integration test.

The test effectively validates config loading, evaluation context setup, individual condition evaluation, and combined expression evaluation with proper assertions.


115-170: LGTM: Thorough CEL conversion and evaluation testing.

The test validates both the generation of CEL expressions from config conditions and the evaluation of those expressions.


172-238: LGTM: Good negative test coverage for precondition failures.

Tests correctly verify that preconditions fail with wrong values and missing fields.


240-264: LGTM: Resource discovery configuration verification.

The test ensures resources with discovery configuration have at least one discovery method defined.


266-359: LGTM: Realistic post-processing evaluation tests.

The test simulates Kubernetes resource structures and validates nested field access, numeric comparisons, and CEL expression evaluation.


361-417: LGTM: Comprehensive null-safety testing.

Tests correctly verify safe handling of missing resources, null intermediate values, and default value fallbacks.


419-450: LGTM: Parameter extraction and validation tests.

The source format validation correctly uses strings.HasPrefix for safe prefix checking, as addressed from previous review feedback.

internal/criteria/cel_evaluator.go (8)

15-49: LGTM: Well-documented CELResult struct with clear error semantics.

The CELResult struct clearly documents when fields are populated and provides convenient HasError() and IsSuccess() helper methods.


51-85: LGTM: Clean CEL environment construction.

The evaluator correctly enables cel.OptionalTypes() for optional chaining support and dynamically registers variables from the context.


87-111: LGTM: Appropriate type inference with DynType fallback.

The function correctly maps common Go types to CEL types and falls back to cel.DynType for nil and unknown types.


131-209: LGTM: Robust error-capturing evaluation with clear categorization.

The EvaluateSafe function correctly captures all error types (parse, program creation, evaluation) in the result rather than returning them, allowing callers to handle failures gracefully. The type-check fallback to parsed AST is appropriate for dynamic types.


257-280: LGTM: Comprehensive empty value detection.

The function correctly handles various CEL value types including null, empty strings, false booleans, and empty collections.


282-318: Acknowledged: Nested exists operator design decision.

The implementation generates has(a.b.c) for nested paths, which can error if intermediate paths don't exist. This is documented in the comments (lines 283-285) and handled by EvaluateSafe() capturing the error. The tests verify this behavior explicitly.

Based on past review discussion, this is an intentional design choice where errors are captured in the result for the caller to handle.


320-385: LGTM: Proper string escaping with strconv.Quote.

The use of strconv.Quote (line 328) correctly handles all escape sequences (quotes, backslashes, newlines, etc.) as addressed from previous review feedback. The reflection fallback provides good coverage for less common types.


387-403: LGTM: Correct multi-condition CEL generation.

The function properly wraps each condition in parentheses and joins with && for correct precedence. Empty conditions returning "true" is appropriate (vacuously true).

internal/criteria/cel_evaluator_test.go (6)

12-113: LGTM: Comprehensive table-driven tests for CEL evaluation.

Good coverage of string equality, numeric comparisons, boolean variables, compound expressions, list membership, empty expressions, and invalid syntax.


115-138: LGTM: Nested data access validation.

The test verifies CEL can correctly evaluate expressions against nested map structures.


140-218: LGTM: Thorough EvaluateSafe error handling tests.

Excellent coverage of error scenarios including missing fields, null value access, and has() semantics for both missing intermediates and missing leaf fields. The conditional logic usage test demonstrates intended API usage patterns.


257-393: LGTM: Comprehensive operator coverage with clear documentation.

The test cases explicitly document the expected CEL output for each operator and nested field scenario. Comments on lines 350-351 clarify that nested field errors are handled by EvaluateSafe at a higher level.


465-497: LGTM: Good edge case coverage for value formatting.

Tests correctly verify handling of supported types (including escape sequences in strings) and unsupported types (channels, typed maps, functions).


499-572: LGTM: Excellent has() semantics documentation through tests.

This test effectively documents the behavior of has() with nested paths:

  • has(data.level1.level2.value) succeeds when all intermediates exist
  • has(data.level1.level2.missing) succeeds with false when parent exists but leaf doesn't
  • has(data.level1.nonexistent.value) errors when intermediate doesn't exist

This serves as both test coverage and behavioral documentation.

internal/config_loader/validator.go (5)

18-50: Validation error aggregation is clear and usable

The ValidationError / ValidationErrors types provide good context (path + message) and aggregate multiple issues into a single error value, which is ideal for config validation UX. No changes needed here.


75-96: Validation pipeline behavior is reasonable

Nil-config guarding plus running all validators and returning a single aggregated ValidationErrors instance gives callers both safety and comprehensive feedback. The choice to continue after initCELEnv failure (while recording the error) is also acceptable since CEL-specific checks then effectively no-op when celEnv is nil.


111-144: Operator validation correctly delegates to criteria helpers

Condition operator validation over preconditions and post when blocks is wired correctly and uses criteria.IsValidOperator / OperatorStrings() for a single source of truth. This should keep config errors around unsupported operators clear and consistent.


241-270: Variable definition lookup handles nested and resource aliases well

isVariableDefined correctly supports exact matches, root-variable checks (e.g., clusterDetails.status.phase vs clusterDetails), and the special resources.<alias> pattern used for discovered resources. This looks aligned with how GetDefinedVariables() is described to populate resource aliases; no issues spotted.


493-498: Public Validate wrapper keeps API backward compatible

Exported Validate(*AdapterConfig) delegating to NewValidator(config).Validate() preserves the previous entry point while enabling the richer Validator type for more advanced use. Nice separation of concerns.

internal/criteria/types.go (2)

7-39: Operator constants and helpers are consistent with CEL conversion

The Operator constants and SupportedOperators slice line up with what ConditionToCEL and the config validator expect (equals, notEquals, in, notIn, contains, greaterThan, lessThan, exists). IsValidOperator / OperatorStrings() are straightforward and provide a single source of truth for validation and error messages. No issues here.


99-115: EvaluationError formatting and unwrapping are appropriate

EvaluationError’s Error() message includes both the field and message (and inner error when present), and Unwrap() exposes the underlying cause for errors.Is/errors.As. This works well with IsFieldNotFound and other higher-level helpers.

internal/config_loader/types.go (3)

3-39: Core config model matches the described adapter surface

AdapterConfig, AdapterConfigSpec, AdapterInfo, and HyperfleetAPIConfig form a clear YAML schema for the adapter, with fields that line up with how the loader and HyperFleet client are described (timeouts, retries, etc.). No structural issues stand out here.


46-63: Parameter and external fetch modeling look flexible and future‑proof

Parameter’s support for raw Build structures, BuildRef/BuildRefContent, and FetchExternalResource gives you a lot of room for advanced parameter derivation without over-constraining the schema. The separation of on-disk refs from loaded content via yaml:"-" is also clean. Looks good.


113-121: Resource / Discovery definitions integrate cleanly with k8s discovery helpers

The Resource and DiscoveryConfig structs align with internal/k8s_client/discovery.go expectations (namespace, name vs selectors, label selector as string map), and ManifestItems being internal-only (yaml:"-") matches how refs are loaded in the validator. No issues here.

internal/criteria/evaluator.go (3)

37-64: Evaluator construction and CEL evaluator caching are well‑structured

NewEvaluator safely handles a nil context by creating a default one, and getCELEvaluator uses sync.Once to lazily initialize and cache the CELEvaluator. This avoids repeated CEL env construction while keeping the API simple.


313-385: Operator implementations cover expected cases; error reporting is clear

  • evaluateEquals / evaluateIn / evaluateContains use reflect.DeepEqual and kind checks appropriately to handle maps, slices/arrays, and strings.
  • evaluateGreaterThan / evaluateLessThan delegate to compareNumbers/toFloat64, giving consistent errors when values aren’t numeric.
  • evaluateExists’s definition (“non‑nil, non‑empty for strings/collections, non‑nil interfaces/pointers, otherwise true”) matches a typical “field presence” semantics rather than truthiness.

Overall, these helpers look correct and error messages are descriptive.


566-582: FieldNotFoundError and IsFieldNotFound now support wrapped errors correctly

FieldNotFoundError provides a simple structured error, and IsFieldNotFound now uses errors.As to detect it even when wrapped (e.g., inside EvaluationError). That matches how higher‑level code wants to treat “field missing” as a distinct class of error and resolves the earlier limitation.

Copy link

@openshift-ci openshift-ci bot left a comment

Choose a reason for hiding this comment

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

@xueli181114: 1 invalid OWNERS file

Details

In response to this:

Summary

Implement core adapter configuration, criteria evaluation, and API client components:

Config Loader: Complete YAML configuration parser with validation for adapter specs, parameters, preconditions, and resources
CEL Criteria Evaluator: Expression evaluation engine for preconditions and conditional logic
HyperFleet API Client: HTTP client with timeout, retry, and authentication for API interactions
Main Application: Integrate all components with command-line config loading and event processing framework

Test Coverage

• Unit tests for all config validation scenarios
• CEL expression evaluation test suite
• API client integration tests with containerized endpoints
• E2E configuration loading tests

Summary by CodeRabbit

  • New Features

  • Configuration management: adapter params, preconditions, resources, post-actions, and templates.

  • HyperFleet API client with configurable timeouts, retries and backoff.

  • Kubernetes discovery API for flexible resource lookup.

  • Semantic validation with CEL expression checks.

  • Configuration Updates

  • New imageTag parameter and streamlined post-action API payload behavior.

  • Renamed broker keys: SUBSCRIPTION_ID → BROKER_SUBSCRIPTION_ID, TOPIC → BROKER_TOPIC.

  • Added deployment and job templates for automation.

  • Infrastructure

  • Improved test container lifecycle and integration test reliability.

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

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@@ -1,5 +1,6 @@
approvers:
Copy link

Choose a reason for hiding this comment

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

Cannot parse file: error converting YAML to JSON: yaml: line 4: could not find expected ':'.

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

🧹 Nitpick comments (10)
test/integration/k8s_client/client_integration_test.go (1)

17-31: Local GVK table looks correct; consider minor tidy-up

The local gvk struct correctly captures core/v1 GVKs for Namespace/Pod/Service/ConfigMap/Secret and cleanly removes the dependency on k8s_client test-only exports. If you find yourself adding more kinds over time, consider using schema.GroupVersion{Group: "", Version: "v1"}.WithKind("...") or a small named helper type to avoid repeating the literals, but this is optional given the current scope.

test/integration/testutil/container.go (1)

157-171: Retry/cleanup behavior looks solid; consider minor context naming cleanup

The per-attempt termination + force cleanup (lines 157-171) and the final t.Cleanup with a minimum 60s timeout (lines 177-205) are a good improvement and should significantly reduce container leaks across retries and at test teardown.

Only a small readability nit: the inner ctx in the retry failure block shadows the outer ctx and differs from the cleanupCtx naming used later. Renaming it avoids confusion and matches the later pattern:

-		if container != nil {
-			t.Logf("Attempt %d failed but container was created. Terminating...", attempt)
-			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-			if termErr := container.Terminate(ctx); termErr != nil {
+		if container != nil {
+			t.Logf("Attempt %d failed but container was created. Terminating...", attempt)
+			termCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+			if termErr := container.Terminate(termCtx); termErr != nil {
 				t.Logf("Warning: Failed to terminate failed container from attempt %d: %v", attempt, termErr)
 				// Try force cleanup
 				if cid := container.GetContainerID(); cid != "" {
 					forceCleanupContainer(t, cid)
 				}
 			}
 			cancel()
 		}

Functionally everything is fine; this is just to reduce cognitive load when reading the nested contexts.

Also applies to: 177-205

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

39-52: Cleanup behavior is correct; double Terminate is acceptable but slightly redundant

The changes here make the envtest lifecycle much safer:

  • TestEnvPrebuilt.Cleanup now actually terminates the container with a 30s timeout and logs (without failing the test) if it was already stopped, which is the right failure mode for cleanup.
  • SetupTestEnvPrebuilt adds a setupSuccess flag and a deferred block that force-terminates the container when setup fails mid-way, giving you "cleanup before assertions" semantics.

Given that testutil.StartContainer already registers a t.Cleanup hook that calls Terminate on the same container, you now have up to three termination call sites (the setup defer, TestEnvPrebuilt.Cleanup, and the t.Cleanup hook). Because Terminate is idempotent, this is functionally safe; the only tradeoff is some potential duplicate log noise.

If you ever want to simplify, you could rely solely on the shared t.Cleanup plus TestEnvPrebuilt.Cleanup and drop the setup-failure defer, but it’s not required for correctness.

Also applies to: 136-147, 186-187

internal/hyperfleet_api/types.go (1)

212-231: Consider adding validation for ClientConfig fields.

The Client interface is clean and well-defined. However, ClientConfig currently lacks validation for:

  • Invalid BackoffStrategy values (arbitrary strings)
  • Negative or zero values for RetryAttempts, BaseDelay, MaxDelay
  • Negative Timeout values

If validation isn't performed at config load time (e.g., in internal/config_loader), consider adding a Validate() method to ClientConfig.

configs/templates/job.yaml (1)

5-12: Discovery labels look correct; double‑check .metadata.name in template context

The added hyperfleet.io/resource-type: "job" and hyperfleet.io/managed-by labels align with the discovery expectations from the adapter config, and the rest of the manifest looks fine.

One thing to verify: ensure .metadata.name is actually present in the render context so hyperfleet.io/managed-by does not end up empty. If this should instead track some other identifier (e.g., adapter or controller name), consider switching to that field.

Also applies to: 18-25

internal/k8s_client/client.go (1)

162-171: Avoid shadowing labelSelector when converting to a selector

The new label‑selector handling and validation are a nice improvement. To keep things clearer, consider using a different name for the selector value instead of reusing labelSelector:

-    selector, err := metav1.ParseToLabelSelector(labelSelector)
+    selector, err := metav1.ParseToLabelSelector(labelSelector)
@@
-    labelSelector, err := metav1.LabelSelectorAsSelector(selector)
+    sel, err := metav1.LabelSelectorAsSelector(selector)
@@
-    opts = append(opts, client.MatchingLabelsSelector{Selector: labelSelector})
+    opts = append(opts, client.MatchingLabelsSelector{Selector: sel})

This avoids confusing the original string parameter with the parsed selector.

Also applies to: 176-195

internal/k8s_client/README.md (1)

1-6: Align the documented package alias with example code

The new discovery and low‑level listing examples are clear and match the client API, but the header refers to k8sclient while the examples use k8s_client as the import alias. Consider standardizing on one alias across the README to avoid minor confusion for consumers copy‑pasting snippets.

Also applies to: 61-74, 184-223

cmd/adapter/main.go (1)

62-81: Consider failing fast on missing or invalid HyperFleet API configuration

You already enforce presence of the API base URL and validate timeout/backoff in createAPIClient. Given the config loader and tests treat the API token/version env vars as required inputs, it may be worth also checking for those at startup and returning an error if they’re missing or malformed, instead of discovering it on the first API call.

This would make misconfiguration obvious and align runtime behavior with the config loader’s validation guarantees.

Also applies to: 190-221

internal/config_loader/loader_test.go (1)

13-1397: Loader tests are comprehensive; consider reducing dependence on error strings

This suite does a great job exercising Load/Parse, parameter/precondition/resource validation, file references, discovery rules, and manifest refs/refs arrays. Behavior is well covered.

A minor future improvement would be to rely less on exact error message substrings (e.g., via typed/sentinel errors or helper predicates), so refactoring error text doesn’t force widespread test updates. Not a blocker for this PR.

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

65-166: Consider extracting a small helper for building []criteria.ConditionDef from config conditions

Multiple tests rebuild the same slice pattern from precond.Conditions into []criteria.ConditionDef (Lines 90‑97, 121‑128, 185‑192, 208‑215, 152‑159). A tiny local helper (e.g., toCriteriaConditions(precond.Conditions)) would reduce duplication and keep future changes to the mapping in one place, while keeping tests readable.

Also applies to: 177-233

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4886d24 and 731aa97.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (47)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (4 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/constants.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (2 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • internal/k8s_client/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • pkg/logger/logger_test.go (1 hunks)
  • scripts/run-integration-tests.sh (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/broker_consumer/testutil_publisher.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (25 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_selector.go (2 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (4 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • internal/criteria/README.md
  • pkg/logger/logger_test.go
  • internal/hyperfleet_api/README.md
🚧 Files skipped from review as they are similar to previous changes (10)
  • test/testdata/templates/deployment.yaml
  • internal/config_loader/validator_test.go
  • test/integration/k8s_client/helper_testcontainers_k3s.go
  • test/integration/config-loader/loader_template_test.go
  • pkg/errors/api_error.go
  • configs/adapter-config-template.yaml
  • configs/templates/cluster-status-payload.yaml
  • internal/criteria/evaluator_scenarios_test.go
  • internal/config_loader/README.md
  • configs/templates/deployment.yaml
🧰 Additional context used
🧬 Code graph analysis (11)
internal/k8s_client/client_test.go (2)
internal/config_loader/types.go (1)
  • DiscoveryConfig (152-156)
internal/k8s_client/discovery.go (2)
  • DiscoveryConfig (32-42)
  • BuildLabelSelector (105-122)
internal/k8s_client/discovery.go (2)
internal/config_loader/types.go (1)
  • DiscoveryConfig (152-156)
internal/k8s_client/client.go (1)
  • Client (20-23)
internal/config_loader/accessors.go (2)
internal/config_loader/types.go (6)
  • AdapterConfig (4-9)
  • Parameter (47-63)
  • Resource (142-149)
  • Precondition (74-81)
  • PostAction (170-174)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/constants.go (1)
  • FieldResources (20-20)
internal/criteria/evaluator_test.go (3)
internal/criteria/types.go (2)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/evaluator.go (5)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
  • EvaluationResult (12-23)
  • IsFieldNotFound (579-582)
  • FieldNotFoundError (567-571)
internal/config_loader/constants.go (1)
  • FieldValue (68-68)
internal/hyperfleet_api/client.go (2)
internal/hyperfleet_api/types.go (10)
  • Client (213-231)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (54-63)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
pkg/errors/api_error.go (1)
  • NewAPIError (123-134)
internal/k8s_client/client.go (1)
pkg/errors/error.go (1)
  • KubernetesError (203-205)
internal/hyperfleet_api/client_test.go (3)
internal/hyperfleet_api/client.go (9)
  • NewClient (100-118)
  • ClientOption (36-36)
  • WithTimeout (65-69)
  • WithRetryAttempts (72-76)
  • WithRetryBackoff (79-83)
  • WithBaseDelay (86-90)
  • WithMaxDelay (93-97)
  • WithDefaultHeader (55-62)
  • WithConfig (46-52)
internal/hyperfleet_api/types.go (8)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • ClientConfig (38-51)
  • Request (70-85)
  • WithHeader (103-110)
  • DefaultClientConfig (54-63)
  • Response (156-169)
pkg/errors/api_error.go (2)
  • NewAPIError (123-134)
  • IsAPIError (144-150)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (2)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
internal/criteria/evaluator.go (2)
  • ConditionDef (291-295)
  • NewEvaluator (48-55)
internal/config_loader/loader_test.go (4)
internal/config_loader/loader.go (7)
  • Load (88-111)
  • Parse (114-130)
  • LoadWithVersion (134-136)
  • ParseWithVersion (140-142)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (62-66)
internal/config_loader/types.go (10)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
  • AdapterConfig (4-9)
  • AdapterConfigSpec (19-27)
  • AdapterInfo (30-32)
  • Parameter (47-63)
  • Resource (142-149)
  • PostConfig (164-167)
  • DiscoveryConfig (152-156)
  • SelectorConfig (159-161)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (471-483)
  • IsSupportedAPIVersion (461-468)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/config_loader/types.go (2)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (4)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
🔇 Additional comments (39)
test/integration/broker_consumer/testutil_publisher.go (1)

39-46: Retry-on-AlreadyExists behavior looks good; just confirm duplicate-publish semantics

The immediate retry after an AlreadyExists error tightens the tests nicely and avoids silently skipping publishes when topic creation races with the subscriber. One thing to double-check: if the underlying broker can both publish the message and still surface an AlreadyExists error from a create call, this pattern would cause the same event to be published twice for that index. That may be fine (at-least-once semantics) but could affect tests that assert an exact message count.

If you’re confident AlreadyExists only happens before any publish attempt is committed, this is solid as-is.

test/integration/k8s_client/client_integration_test.go (2)

65-65: Consistent use of SetGroupVersionKind with local gvk

All updated SetGroupVersionKind calls now consistently use the local gvk constants for core resources, which keeps the tests explicit about the resource type while remaining decoupled from internal helpers. This matches the inline apiVersion/kind in the objects and looks good.

Also applies to: 99-99, 129-129, 176-176, 215-215, 257-257, 309-309, 370-370, 423-423, 464-464, 496-497, 512-513, 546-547, 576-576, 628-628, 697-697, 728-728


135-136: GVK migration requires manual verification

The migration of client calls to use GVK patterns should be verified to ensure complete removal of CommonResourceKinds references and correct alignment of GVK usages across all test locations (lines 135-136, 145-146, 184-185, 263-264, 267-268, 272-273, 316-317, 330-331, 336-337, 340-341, 388-389, 435-437, 445-447, 471-473, 523-525, 528-530, 551-553, 555-556, 586-587, 638-639, 705-707).

Confirm by running: rg -n "CommonResourceKinds" .

test/integration/testutil/container.go (1)

237-305: New CLI-based cleanup helpers are reasonable for test-only use

The new forceCleanupContainer and CleanupLeakedContainers helpers provide useful last-resort cleanup for stubborn or leaked containers in integration tests:

  • They avoid shell invocation and only pass container IDs / filters as separate args to exec.Command, which is safe.
  • They try docker then podman, matching the documented expectations.
  • forceCleanupContainer now logs all failures instead of attempting to parse error strings, which is more robust across runtimes and versions.

Given these are test-only utilities, the behavior (including noisy logs when runtimes are missing or containers are already gone) is acceptable. I don’t see correctness issues here.

internal/k8s_client/types.go (1)

48-50: LGTM! Good defensive programming.

The nil guard prevents potential nil pointer dereferences and returns a safe default (empty GroupVersionKind) when the input is nil.

scripts/run-integration-tests.sh (1)

43-43: LGTM! Improved error visibility.

The grep-based image search is more flexible, and removing the 2>/dev/null error suppression allows errors to propagate for better debugging.

configs/README.md (1)

25-25: LGTM! Documentation aligned with configuration changes.

The environment variable name update from SUBSCRIPTION_ID to BROKER_SUBSCRIPTION_ID is consistent with the ConfigMap template changes in this PR.

Also applies to: 62-62, 67-67

test/integration/broker_consumer/adapter_integration_test.go (2)

95-95: LGTM! Appropriate timeout increase.

The 60-second timeout accommodates the new polling mechanism and longer startup times in containerized test environments.


124-158: Excellent improvement! Polling eliminates race conditions.

Replacing the fixed sleep with active polling and ticker-based retry eliminates the race condition where the subscriber may not be fully ready immediately after Subscribe() returns. The 500ms retry interval and context-driven termination are well-balanced.

configs/broker-configmap-pubsub-template.yaml (1)

24-24: LGTM! Consistent naming convention.

The ConfigMap key renames (SUBSCRIPTION_IDBROKER_SUBSCRIPTION_ID, TOPICBROKER_TOPIC) establish a consistent BROKER_ prefix and align with the documentation updates in this PR.

Also applies to: 28-28, 93-97

internal/hyperfleet_api/types.go (6)

12-22: LGTM! Well-defined backoff strategy enum.

The BackoffStrategy type with exponential, linear, and constant options provides clear, documented retry behavior.


24-31: LGTM! Reasonable default values.

The default configuration (10s timeout, 3 retries, exponential backoff, 1s base delay, 30s max delay) provides a good balance between resilience and responsiveness.


37-63: LGTM! Clean configuration structure.

The ClientConfig struct is comprehensive, and the DefaultClientConfig factory properly initializes the DefaultHeaders map.


69-149: LGTM! Idiomatic functional options pattern.

The Request type and functional options (WithHeaders, WithBody, WithJSONBody, etc.) follow Go best practices and provide flexible per-request configuration.


186-198: LGTM! Correct retry logic.

The IsRetryable method correctly identifies retryable HTTP status codes (408 Request Timeout, 429 Too Many Requests, and all 5xx server errors).


155-206: LGTM! Useful response utilities.

The Response helper methods (IsSuccess, IsClientError, IsServerError, IsRetryable, BodyString) provide convenient status code classification and body access.

test/testdata/adapter_config_valid.yaml (5)

16-20: LGTM! Appropriate API client configuration.

The hyperfleetApi section with timeout (2s), retry attempts (3), and exponential backoff provides a good configuration for testing the new API client integration.


24-51: LGTM! Comprehensive parameter definitions.

The params section covers all required sources (env., event.) with proper typing, defaults, and required flags. This provides good test coverage for parameter extraction.


53-89: LGTM! Well-structured preconditions.

The preconditions demonstrate both API-based preconditions (with extraction and structured conditions) and CEL expression-based validation, providing comprehensive test coverage.


91-134: LGTM! Diverse resource discovery scenarios.

The resources section tests multiple discovery patterns (bySelectors with labelSelector, byName, and template refs), covering key use cases for the resource discovery functionality.


136-202: LGTM! Complete post-processing workflow.

The post section demonstrates param building with CEL expressions for conditions and data, plus postActions with API calls, providing end-to-end test coverage for the post-processing pipeline.

test/integration/k8s_client/helper_selector.go (1)

14-15: Explicit GVK for the default namespace looks good

Using schema.GroupVersionKind on the unstructured Namespace makes the test setup self-contained and avoids relying on test-only helper constants. No issues here.

Also applies to: 77-87

internal/k8s_client/client_test.go (1)

9-46: DiscoveryConfig and label selector tests are well‑targeted

The tests exercise single vs list vs cluster‑scoped discovery and validate deterministic selector ordering, which aligns well with the DiscoveryConfig and BuildLabelSelector behavior. No changes needed.

Also applies to: 48-96

internal/criteria/evaluator_test.go (1)

11-1060: Evaluator test coverage is strong and consistent with the API

These tests exercise the full surface of the evaluator: simple and composite conditions, numeric and collection operators, nested field access, null‑safety, error typing (FieldNotFoundError), and the richer *WithResult paths. The behavior around null paths and missing fields is especially well nailed down. No changes needed from a correctness perspective.

cmd/adapter/main.go (1)

60-81: Avoid logging full event payloads at info level

Event payload logging should not emit entire event bodies, as this can expose sensitive data and create excessive log noise. The review requires verification of the actual event handling code at lines 140-165 to confirm whether log.Infof("Received event: ... data=%s", string(evt.Data())) or similar patterns exist and at what log level they operate.

Consider either:

  • logging only selected, non‑sensitive fields, or
  • downgrading full‑payload logging to debug/trace‑level with a guard so it's disabled in production.
internal/k8s_client/discovery.go (1)

1-123: LGTM! Clean discovery interface implementation.

The Discovery interface and DiscoveryConfig provide a well-designed abstraction for resource discovery. Key strengths:

  • Proper nil-safety (lines 79-81)
  • Single-resource and selector-based discovery paths are cleanly separated
  • BuildLabelSelector produces deterministic output via key sorting (lines 111-115)
  • Error handling is appropriate throughout
internal/config_loader/validator.go (1)

1-496: LGTM! Comprehensive validation framework.

The Validator implementation provides thorough semantic validation across operators, templates, CEL expressions, and Kubernetes manifests. Key strengths:

  • Well-structured error aggregation via ValidationErrors
  • Nil-safety and defensive checks throughout
  • Proper integration with CEL environment for expression validation
  • Recursive template and build expression validation
  • Special handling for resource aliases (lines 259-264)
internal/config_loader/validator_schema.go (1)

1-523: LGTM! Robust schema validation with proper path traversal protection.

The SchemaValidator provides comprehensive structural validation with proper error handling:

  • Fail-fast validation pipeline (lines 34-48)
  • Thorough resource discovery validation (lines 164-202)
  • File reference validation and loading with proper error aggregation
  • Path traversal protection correctly implemented (lines 410-437) as noted in past review
internal/config_loader/accessors.go (1)

1-278: LGTM! Well-designed accessor layer with comprehensive nil-safety.

The accessor methods provide clean, nil-safe access to config data:

  • Comprehensive variable collection in GetDefinedVariables (lines 34-82)
  • Consistent nil-checking pattern across all accessors
  • Robust map normalization handling both yaml.v2 and yaml.v3 formats (lines 259-277)
  • Proper error reporting with type information (line 248)
internal/criteria/types.go (1)

1-116: LGTM! Clean criteria evaluation type definitions.

The types provide a solid foundation for the criteria evaluation system:

  • Well-defined operator constants covering common comparison operations
  • EvaluationContext with proper nil-safety in Merge (line 91-93)
  • EvaluationError following standard error wrapping patterns
  • Clean separation of concerns
internal/hyperfleet_api/client.go (1)

1-385: LGTM! Robust HTTP client with proper retry and backoff logic.

The client implementation addresses previous review feedback and provides solid retry semantics:

  • Retry attempts normalized to minimum of 1 (lines 150-152), addressing the zero-attempt concern
  • Concurrency-safe jitter calculation using package-level rand.Float64() (line 308)
  • Proper context cancellation handling (lines 165-167, 194-196)
  • Clean functional options pattern for configuration
  • APIError integration for comprehensive error reporting
internal/criteria/cel_evaluator.go (1)

1-404: LGTM! Comprehensive CEL evaluation with proper error handling.

The CEL evaluator provides robust expression evaluation:

  • Safe error handling via EvaluateSafe that captures errors in CELResult rather than panicking (lines 131-209)
  • Proper string escaping using strconv.Quote (line 328)
  • Dynamic type inference and environment building
  • For the exists operator on nested paths (lines 307-314), errors are intentionally captured in the result per author's confirmation, allowing callers to handle missing fields appropriately
internal/config_loader/loader.go (1)

1-198: LGTM! Clean config loader with modular validation pipeline.

The loader implementation provides a well-structured configuration loading flow:

  • Functional options pattern for flexible configuration (lines 45-73)
  • Automatic baseDir resolution from config file path (lines 102-106)
  • Modular validation pipeline with optional steps (lines 152-198)
  • Correct YAML import path (gopkg.in/yaml.v3 at line 8)
  • Proper delegation between Load and Parse
internal/criteria/cel_evaluator_test.go (1)

12-572: CEL evaluator tests look comprehensive and well-structured

The test suite exercises construction, basic and nested evaluation, error-handling via EvaluateSafe, CEL translation, and value formatting with good coverage and clear expectations; I don't see issues that would block merge here.

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

28-447: Integration tests provide strong end-to-end coverage for config + criteria behavior

These integration tests exercise real config loading, preconditions, CEL conversion, resource discovery fields, post-processing with nested K8s-like data, null safety, and parameter extraction. The scenarios around failing preconditions and missing resources align well with the evaluator’s null-safe helpers and should catch regressions in both config shape and evaluation semantics.

internal/config_loader/constants.go (1)

7-117: Centralized field-name constants look consistent with the config schema

The constant set mirrors the YAML tags in types.go (spec, params, preconditions, resources, discovery, post, manifests) and should help avoid scattered string literals; I don’t see mismatches in the values based on the surrounding types.

internal/hyperfleet_api/client_test.go (1)

18-642: HTTP client test suite thoroughly exercises configuration, retries, and error handling

The tests cover client construction, all HTTP verbs, header merging, retry and no‑retry behavior, timeout/cancellation, backoff calculation, and APIError helpers in a clear, isolated way using httptest; I don’t see correctness or reliability issues here.

internal/config_loader/types.go (1)

3-186: Config schema types and YAML tags look coherent and match usage

The type definitions (adapter/spec, parameters, preconditions, resources, discovery, post actions) and their YAML tags form a consistent schema, and the Condition custom UnmarshalYAML correctly handles both value and values with clear precedence; I don’t see structural issues here.

internal/criteria/evaluator.go (1)

327-345: Verify nil-safety in evaluateIn and evaluateContains against actual implementation

The review's specific claim that reflect.ValueOf(nil).Kind() panics is incorrect. Go's reflect package returns an invalid reflect.Value for nil, and calling Kind() safely returns reflect.Invalid without panicking.

However, the broader concern about nil-safety is still valid. Calling other reflect methods like Len(), Index(), Interface(), or IsNil() on invalid reflect.Value objects will panic. The review's snippet shows list.Len() and list.Index(i) called immediately after the kind check, which would panic if expectedList is nil.

To properly assess the issue:

  1. Confirm whether the implementations call Len(), Index(), Interface(), or similar methods on potentially-invalid reflect Values
  2. Verify whether nil inputs are actually reachable through normal caller paths or only through edge cases
  3. If panics are reachable, the suggested nil checks are appropriate; if not, the issue may be lower priority or moot

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

🧹 Nitpick comments (11)
internal/k8s_client/README.md (2)

61-73: Clarify mutual exclusivity of DiscoveryConfig fields.

The examples show LabelSelector and ByName as separate patterns, but the documentation doesn't explicitly state whether both can be provided simultaneously, which takes precedence, or if exactly one is required. This could lead to ambiguous usage.

Consider adding a note clarifying the field precedence or constraints (e.g., "Provide either LabelSelector for bulk discovery or ByName for single-resource retrieval; if both are set, ByName takes precedence").


184-212: Document the Discover Resources section well; minor suggestion for clarity.

The new section clearly distinguishes between bulk discovery (by label selector) and single-resource retrieval (by name), which aligns well with the API design. The examples are practical and easy to follow.

Consider adding a brief note on when to use DiscoverResources vs. ListResources (e.g., when resource discovery configuration is externalized in adapter config).

internal/k8s_client/client_test.go (1)

9-46: Consider adding edge case tests for conflicting or empty configurations.

The current tests cover the primary scenarios well. However, consider adding tests for:

  • Both ByName and LabelSelector populated (to verify precedence/behavior)
  • All fields empty (to verify default behavior)
  • ByName set without Namespace (cluster-scoped single resource)

These edge cases would strengthen the test suite and document expected behavior under unusual configurations.

Example test case to add:

t.Run("conflicting ByName and LabelSelector", func(t *testing.T) {
	d := &DiscoveryConfig{
		Namespace:     "default",
		ByName:        "my-resource",
		LabelSelector: "app=myapp",
	}
	
	// Document expected behavior when both are set
	assert.True(t, d.IsSingleResource()) // or False, depending on design
	// Verify which field takes precedence
})
test/integration/testutil/container.go (2)

237-265: Consider exit-code checking to reduce log noise.

The current implementation logs all errors, including "no such container" cases (line 259). While this approach is acceptable, parsing the exit code would allow you to suppress expected "not found" errors and only log genuine failures.

For example, Docker/Podman typically return exit code 1 for "container not found" during rm. You could check exitErr.ExitCode() to distinguish this from other errors.

This is consistent with the past review suggestion to either use exit codes or accept log noise. The current approach (accepting noise) works, but exit-code checking would be cleaner.

Based on learnings, past review suggested using exit codes for more precise error handling.


267-305: Optional: Improve observability and completeness.

Two optional refinements for this utility function:

  1. Log runtime availability (lines 280-282): When listCmd.Output() fails, the function silently tries the next runtime. Logging which runtimes were attempted (e.g., "docker not available, trying podman") would help users understand cleanup behavior.

  2. Check all runtimes (line 303): The function returns after cleaning up with the first successful runtime. If tests have leaked containers in both Docker and Podman, only one runtime's leaks are cleaned. Consider removing the early return and checking both runtimes to ensure comprehensive cleanup.

Example for logging:

 		output, err := listCmd.Output()
 		if err != nil {
+			t.Logf("No containers found with %s (or %s not available)", runtime, runtime)
 			continue // Try next runtime
 		}

Example for checking all runtimes:

 				t.Logf("Cleaned up leaked container: %s", id)
 			}
 		}
-		return // Success with this runtime
 	}
 }
test/integration/broker_consumer/adapter_integration_test.go (1)

95-157: Polling helper nicely removes fixed‑sleep flakiness in the smoke test.

Using a context‑bound ticker loop around publishTestMessages plus the buffered messageReceived channel gives a clean, race‑free way to wait for readiness. If you ever see CI timeouts here, you might optionally log ctx.Err() inside the helper before returning false, but the current implementation is already solid.

internal/config_loader/validator_schema.go (1)

75-223: Schema validator and path‑safe file handling look solid; two small nits to consider.

  • The structural validators (validateAPIVersionAndKind, metadata/adapter/params/preconditions/resources/post actions) are cleanly factored and fail‑fast; combined with ValidateAdapterVersion they give a nice, centralized schema gate.
  • The file‑reference layer (validateFileReferences, loadFileReferences, and resolvePath) now properly constrains paths to baseDir and produces descriptive errors for missing/dir paths, which addresses path traversal concerns.

Two minor follow‑ups you might consider:

  1. In validateResources you loop as for i, resource := range v.config.Spec.Resources and then call validateResourceDiscovery(&resource, path). That pointer is to a copy, which is fine today because validateResourceDiscovery is read‑only, but it could be surprising if someone later tries to mutate the resource there. Either accept Resource by value in validateResourceDiscovery or iterate by index and pass &v.config.Spec.Resources[i] for future‑proofing.
  2. validateParams currently only walks config.Spec.Params. If you want spec.post.params to enjoy the same structural guarantees (non‑empty name and at least one of source/build/buildRef/fetchExternalResource), it may be worth adding a parallel check for Spec.Post.Params or factoring a shared helper; right now, post params are only checked for file existence when they use buildRef.

These are polish-level concerns; the current behavior is correct for existing callers.

Also applies to: 249-437

internal/config_loader/loader_test.go (1)

13-1396: Loader/validator test coverage is thorough and well‑targeted.

These tests collectively hit happy paths, a wide range of structural error conditions, file‑reference handling (incl. manifest.ref[s] and buildRef content), version checks, and condition aliasing, which gives good confidence in the loader + schema validator behavior. If the schema evolves further and the repeated “minimal valid config” YAML starts to drift, you might later factor a small helper to generate those snippets, but it’s not urgent given the current size.

internal/config_loader/accessors.go (1)

12-21: Consider returning a copy of builtinVariables to prevent mutation.

BuiltinVariables() returns the internal slice directly. A caller could accidentally mutate it, affecting all subsequent calls.

 func BuiltinVariables() []string {
-	return builtinVariables
+	result := make([]string, len(builtinVariables))
+	copy(result, builtinVariables)
+	return result
 }
cmd/adapter/main.go (1)

190-221: Remove stray empty line and consider logging the parsed timeout.

Minor formatting issue at line 200 with an empty line after the if err != nil block.

 	if timeout > 0 {
-		
 		opts = append(opts, hyperfleet_api.WithTimeout(timeout))
 	}
internal/criteria/types.go (1)

41-49: Optional: Consider map-based operator validation for better scalability.

The current linear search is fine for 8 operators, but if the operator set grows significantly, consider using a map for O(1) lookup:

var operatorSet = map[string]bool{
    "equals": true,
    "notEquals": true,
    // ... etc
}

func IsValidOperator(op string) bool {
    return operatorSet[op]
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 731aa97 and 782134c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (47)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (1 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (4 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/constants.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (2 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • internal/k8s_client/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • pkg/logger/logger_test.go (1 hunks)
  • scripts/run-integration-tests.sh (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/broker_consumer/testutil_publisher.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (25 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_selector.go (2 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (1 hunks)
  • test/integration/testutil/container.go (6 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • internal/config_loader/README.md
  • internal/criteria/README.md
🚧 Files skipped from review as they are similar to previous changes (19)
  • internal/k8s_client/types.go
  • scripts/run-integration-tests.sh
  • internal/config_loader/validator_test.go
  • test/integration/k8s_client/helper_envtest_prebuilt.go
  • test/integration/broker_consumer/testutil_publisher.go
  • test/integration/k8s_client/helper_testcontainers_k3s.go
  • configs/templates/cluster-status-payload.yaml
  • internal/k8s_client/discovery.go
  • configs/README.md
  • internal/k8s_client/client.go
  • internal/hyperfleet_api/README.md
  • internal/config_loader/validator.go
  • configs/templates/job.yaml
  • internal/criteria/cel_evaluator_test.go
  • test/integration/config-loader/loader_template_test.go
  • test/integration/k8s_client/helper_selector.go
  • configs/templates/deployment.yaml
  • pkg/logger/logger_test.go
  • internal/config_loader/constants.go
🧰 Additional context used
🧬 Code graph analysis (11)
internal/criteria/evaluator_test.go (3)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
  • EvaluationError (100-104)
internal/criteria/evaluator.go (5)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
  • EvaluationResult (12-23)
  • IsFieldNotFound (579-582)
  • FieldNotFoundError (567-571)
internal/config_loader/constants.go (1)
  • FieldValue (68-68)
test/integration/config-loader/config_criteria_integration_test.go (6)
internal/config_loader/loader.go (1)
  • Load (88-111)
internal/criteria/types.go (4)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorExists (26-26)
  • OperatorGreaterThan (22-22)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
internal/config_loader/constants.go (1)
  • FieldValue (68-68)
internal/criteria/cel_evaluator.go (2)
  • ConditionsToCEL (388-403)
  • ConditionToCEL (286-318)
internal/k8s_client/discovery.go (1)
  • Discovery (14-29)
internal/config_loader/accessors.go (2)
internal/config_loader/types.go (5)
  • Parameter (47-63)
  • Resource (142-149)
  • Precondition (74-81)
  • PostAction (170-174)
  • HyperfleetAPIConfig (35-39)
internal/config_loader/constants.go (1)
  • FieldResources (20-20)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/criteria/evaluator.go (1)
  • ConditionDef (291-295)
internal/hyperfleet_api/types.go (1)
internal/k8s_client/client.go (1)
  • ClientConfig (26-35)
cmd/adapter/main.go (4)
internal/config_loader/loader.go (2)
  • EnvConfigPath (22-22)
  • Load (88-111)
internal/config_loader/types.go (2)
  • Metadata (12-16)
  • HyperfleetAPIConfig (35-39)
internal/hyperfleet_api/client.go (5)
  • ClientOption (36-36)
  • WithTimeout (65-69)
  • WithRetryAttempts (72-76)
  • WithRetryBackoff (79-83)
  • NewClient (100-118)
internal/hyperfleet_api/types.go (5)
  • Client (213-231)
  • BackoffStrategy (13-13)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
internal/hyperfleet_api/client.go (1)
internal/hyperfleet_api/types.go (10)
  • Client (213-231)
  • ClientConfig (38-51)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (54-63)
  • Request (70-85)
  • Response (156-169)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (88-88)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (4-9)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (471-483)
internal/config_loader/validator.go (1)
  • Validate (494-496)
internal/config_loader/types.go (2)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/hyperfleet_api/client_test.go (3)
internal/hyperfleet_api/client.go (2)
  • NewClient (100-118)
  • ClientOption (36-36)
internal/hyperfleet_api/types.go (5)
  • ClientConfig (38-51)
  • Request (70-85)
  • WithHeader (103-110)
  • DefaultClientConfig (54-63)
  • Response (156-169)
pkg/errors/api_error.go (2)
  • NewAPIError (123-134)
  • IsAPIError (144-150)
internal/criteria/evaluator.go (3)
internal/config_loader/constants.go (2)
  • FieldValue (68-68)
  • FieldByName (89-89)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (388-403)
🔇 Additional comments (58)
internal/k8s_client/README.md (1)

214-230: "List Resources (Low-Level)" section is appropriately positioned.

The restructured section clearly signals that ListResources is a lower-level API, helping users understand the difference from DiscoverResources and guiding them toward the Discovery-based workflow where appropriate.

internal/k8s_client/client_test.go (2)

1-7: LGTM!

The package declaration and imports are clean and appropriate for unit testing.


48-96: LGTM!

Excellent table-driven test design. The test cases thoroughly cover nil, empty, single, and multiple label scenarios, and correctly verify deterministic alphabetical sorting. The inline comments explaining the expected sort order add helpful clarity.

configs/broker-configmap-pubsub-template.yaml (3)

93-93: Environment variable and configMapKeyRef updated consistently.

The example deployment correctly references the renamed keys (BROKER_SUBSCRIPTION_ID) in both the environment variable definition (line 93) and the configMapKeyRef (line 97).

Also applies to: 97-97


1-159: Template documentation is clear and well-structured.

The ConfigMap template is well-documented with clear sections for adapter-specific configuration, broker library configuration, example deployment, and optional GCP service account setup. YAML syntax is valid, and comments effectively explain usage and customization options.


24-24: Verify that consuming code has been updated to use the new environment variable names.

The renaming from SUBSCRIPTION_IDBROKER_SUBSCRIPTION_ID and TOPICBROKER_TOPIC introduces a breaking change. All code that reads these environment variables (broker consumer, adapter entrypoint, deployment manifests, etc.) must be updated to reference the new names. Without confirmation that consuming code has been updated, existing deployments will fail to find these configuration keys.

test/integration/k8s_client/client_integration_test.go (2)

17-31: LGTM! Well-structured test isolation.

The local gvk struct cleanly decouples integration tests from production package test exports, which is a best practice. All GroupVersionKind values are correct for core v1 resources.


65-65: Consistent GVK usage across all test cases.

All SetGroupVersionKind and client method calls correctly reference the local gvk definitions. The refactor is applied consistently throughout the test suite.

Also applies to: 99-99, 129-129, 135-135, 176-176, 184-184, 215-215, 257-257, 263-263, 267-267, 309-309, 316-316, 370-370, 388-388, 423-423, 435-435, 464-464, 496-496, 512-512, 523-523, 546-546, 576-576, 628-628, 697-697, 728-728

test/integration/testutil/container.go (3)

9-10: LGTM!

The new imports support the CLI-based container cleanup fallbacks introduced later in the file.


39-40: LGTM! Consistent 60s default.

The CleanupTimeout default has been updated to 60s across the comment, DefaultContainerConfig, and zero-value fallback, aligning with the enforced minimum at lines 188-190. This addresses the past review comment about the inconsistency.

Also applies to: 77-77, 107-107


179-204: LGTM! Robust cleanup with proper fallbacks.

The enhanced cleanup logic correctly:

  • Captures container ID upfront for reliable cleanup
  • Enforces the documented 60s minimum timeout
  • Falls back to force cleanup if graceful termination fails
  • Provides clear logging with container IDs
test/testdata/templates/deployment.yaml (1)

1-27: Template fixture looks consistent and well-formed.

Deployment YAML and label/namespace templating line up with the discovery patterns used in the config tests; nothing blocking here.

go.mod (1)

5-143: Dependency set looks consistent with the new config/CEL functionality.

The added direct and indirect requirements align with the new loader, YAML, and CEL usage; I don’t see anything obviously problematic here. Please just ensure CI runs go test ./... and go mod tidy so go.sum and the module graph stay in sync.

pkg/errors/api_error.go (1)

14-150: Rich API error type is well‑designed and integrates cleanly with errors.Is/As.

The APIError struct, helper predicates, and IsAPIError wrapper give callers good observability and control over HTTP failures without complicating the call sites. No issues from my side.

internal/config_loader/loader.go (1)

15-198: Loader and validation pipeline are well‑structured and align with the new schema validator.

Deriving baseDir in Load and prepending WithBaseDir (while still allowing override) is a nice touch, and runValidationPipeline cleanly separates structural, version, file‑reference, and semantic validation phases with good error wrapping. No changes needed here.

test/testdata/adapter_config_valid.yaml (1)

1-202: Canonical “valid” config exercises the full adapter surface; one small semantic nuance to confirm.

This fixture wires together params, preconditions (HTTP + extracted fields + conditions/CEL), three resources (including a manifest.ref template), and a post action with a built payload in a way that matches the new schema and discovery rules, so it’s a good end‑to‑end “golden” config.

One nuance to be aware of: hyperfleetApiVersion is marked required: true but also has default: "v1". If your parameter resolution treats “required” as “must have a non‑zero value after applying defaults,” this is fine; if it instead means “the upstream source (env) must be present,” you may want to either drop the default or mark it non‑required for clarity. Just worth confirming this matches your intended semantics.

internal/config_loader/types.go (7)

1-27: LGTM - Well-structured top-level configuration types.

The AdapterConfig, Metadata, and AdapterConfigSpec structs provide a clean representation of the adapter YAML schema with appropriate YAML tags and optional field handling.


29-44: LGTM - API and Kubernetes configuration types are properly defined.

The AdapterInfo, HyperfleetAPIConfig, and KubernetesConfig structs correctly model their respective configuration sections.


46-71: LGTM - Parameter and FetchExternalResource types support flexible configuration.

Good use of yaml:"-" tag on BuildRefContent to prevent serialization of runtime-populated content. The interface{} types for Build and Default allow schema flexibility.


73-104: LGTM - Precondition and API call types are comprehensive.

The Precondition, APICall, Header, and ExtractField structs cover the necessary fields for API-based precondition evaluation with proper timeout and retry configuration.


106-139: LGTM - Custom unmarshaling handles dual-value support elegantly.

The UnmarshalYAML implementation correctly prioritizes values over value when both are specified, providing backward compatibility and flexibility.


163-186: LGTM - Post-processing types are well-defined.

The PostConfig, PostAction, WhenExpr, and ManifestRef types properly model the post-processing configuration with conditional execution support.


141-161: Potential naming collision with k8s_client.DiscoveryConfig.

Both config_loader.DiscoveryConfig and k8s_client.DiscoveryConfig exist with similar but not identical structures. The k8s_client version uses ByName and LabelSelector string fields, while this version uses ByName and BySelectors with a nested SelectorConfig. This may cause confusion when converting between types.

Consider either:

  1. Renaming this type to ResourceDiscoveryConfig for clarity
  2. Adding conversion methods to bridge between the two types
  3. Documenting the relationship between these types
internal/config_loader/accessors.go (6)

34-82: LGTM - Comprehensive variable collection with proper nil-safety.

GetDefinedVariables() correctly aggregates variables from all sources (built-ins, params, precondition extracts, post params, and resource aliases) with appropriate nil checks.


84-148: LGTM - Accessor methods are well-implemented.

All GetXxxByName methods follow consistent patterns with proper nil-safety and return pointers to avoid unnecessary copies. Using range with index (for i := range) is correct for returning pointers to slice elements.


150-172: LGTM - Name accessor methods are straightforward.

ParamNames() and ResourceNames() correctly pre-allocate slices and return names in order.


178-185: LGTM - ParseTimeout handles empty string correctly.

The method returns 0, nil for empty strings, allowing callers to apply their own defaults. This is well-documented in the comment.


191-249: LGTM - Manifest accessors handle multiple formats correctly.

The HasManifestRef, GetManifestRefs, and UnmarshalManifest methods properly handle both single ref and refs array formats, with appropriate nil-safety and type assertions.


259-277: LGTM - Robust map normalization for YAML compatibility.

normalizeToStringKeyMap correctly handles both map[string]interface{} (yaml.v3) and map[interface{}]interface{} (yaml.v2) formats, with a fallback for non-string keys using fmt.Sprintf.

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

123-128: LGTM - New imageTag parameter follows established patterns.

The parameter is properly configured with source, type, default value, and description. Setting required: false with a default is appropriate for optional configuration.


384-388: Verify authentication handling for reportClusterStatus API call.

The reportClusterStatus post-action has no headers configured in the YAML. Confirm whether Authorization and Content-Type headers are injected at the API client level or at runtime, as they are not explicitly defined here.

internal/criteria/evaluator_test.go (7)

1-9: LGTM - Clean test file setup with appropriate dependencies.

Imports are minimal and focused on testing needs (testify/assert, testify/require).


113-170: LGTM - Comprehensive TestEvaluateEquals covers key edge cases.

Tests include equal/unequal strings, numbers, nil handling (both nil, one nil), providing good coverage for the equals operator.


272-341: Consider adding a test for zero field value in TestEvaluateGreaterThan.

Good coverage overall. The zero-value tests (lines 311-327) are particularly valuable for catching edge cases.


414-468: LGTM - TestEvaluateExists covers various types comprehensively.

Tests correctly verify that empty strings, empty slices, empty maps, and nil values return false, while non-empty values and numbers return true.


820-863: LGTM - TestEvaluateConditionsWithResult validates result structure thoroughly.

Tests verify that extracted fields are populated even when conditions fail, and that FailedCondition index is correctly set.


959-1018: LGTM - Excellent null-safety test coverage.

TestNullHandling thoroughly tests various null path scenarios including accessing fields on null parents, safe accessors, default values, HasField behavior, and safe evaluation methods.


1020-1047: LGTM - Deep null path handling tests are thorough.

TestDeepNullPath validates behavior when null appears in the middle of a path, ensuring graceful degradation with proper error types and safe accessor behavior.

cmd/adapter/main.go (4)

30-35: LGTM - Clean flag definition with environment variable fallback documentation.

The help text clearly documents the ADAPTER_CONFIG_PATH environment variable alternative.


62-91: LGTM - Robust configuration loading with proper error handling.

The startup sequence correctly:

  1. Loads config with version annotation
  2. Validates API base URL presence
  3. Logs configuration details
  4. Creates API client with proper error propagation

140-165: LGTM - Handler scaffold with clear implementation guidance.

The TODO comments provide comprehensive guidance for future implementation. Using _ = adapterConfig and _ = apiClient is appropriate for work-in-progress code to avoid unused variable errors while maintaining the correct closure captures.


209-218: LGTM - Backoff strategy validation is correct.

The switch statement properly validates against the three supported strategies and provides a clear error message for invalid values.

internal/criteria/evaluator_scenarios_test.go (1)

1-363: LGTM! Well-structured scenario tests with proper isolation.

The test suite comprehensively exercises the criteria evaluation system with realistic scenarios. Good practices observed:

  • Isolated contexts per subtest (lines 133-136, 333-338) prevent state mutation
  • Clear test case structure with descriptive names
  • Coverage of edge cases (terminating cluster, null safety, node count boundaries)
internal/criteria/types.go (1)

1-116: LGTM! Clean type definitions with appropriate helpers.

The operator system and evaluation context are well-designed:

  • Clear operator constants with documentation
  • Helpful utility functions (IsValidOperator, OperatorStrings)
  • EvaluationError provides proper error wrapping with Unwrap()
  • EvaluationContext methods are straightforward and safe
internal/hyperfleet_api/client.go (3)

149-152: Good fix: Retry attempts normalized to ensure at least one request.

This properly addresses the previous review concern about zero/negative retry attempts. The normalization ensures the client always makes at least one HTTP request.


306-309: Good fix: Concurrency-safe jitter calculation.

Using package-level rand.Float64() correctly addresses the previous data race concern. The comment clearly explains the concurrency safety.


254-257: LGTM! Smart Content-Type inference.

Automatically setting Content-Type: application/json for requests with bodies when not explicitly set is a good developer experience improvement.

internal/criteria/cel_evaluator.go (3)

327-328: Good fix: Proper string escaping with strconv.Quote.

Using strconv.Quote correctly handles all necessary escape sequences (backslash, quotes, newlines, control characters), addressing the previous review concern.


307-314: Acknowledged: Nested exists operator generates non-null-safe CEL.

As discussed in previous reviews, the exists operator for nested paths generates has(a.b.c) which can error if intermediate paths are null. The author confirmed this is intentional - errors are captured in CELResult.Error and CELResult.ErrorReason rather than panicking, allowing callers to handle missing fields appropriately via EvaluateSafe().


131-140: Verify: Empty expression evaluates to true.

An empty or whitespace-only expression returns true with Matched=true. This might be surprising to users expecting a validation error instead.

Is this default-allow semantics intentional for your use case?

internal/hyperfleet_api/types.go (1)

1-232: LGTM! Well-designed HTTP client types.

The type system is clean and comprehensive:

  • Clear separation of concerns (config, request, response, client interface)
  • Functional options pattern for flexible configuration
  • Sensible retry logic (408, 429, 5xx are retryable)
  • Good developer ergonomics with helper methods
  • Per-request overrides for timeout/retry settings
test/integration/config-loader/config_criteria_integration_test.go (2)

439-443: Good fix: Safe prefix checking without panic risk.

Using strings.HasPrefix eliminates the out-of-bounds slice panic risk identified in the previous review. The validation now safely handles all source string lengths.


1-447: LGTM! Comprehensive integration test coverage.

The test suite thoroughly exercises the config loading and criteria evaluation flow:

  • Realistic scenarios with simulated runtime data
  • Good null safety testing (lines 357-413)
  • Proper use of build tags for integration tests
  • Clear test organization with descriptive names
  • Environment-aware config path resolution
internal/hyperfleet_api/client_test.go (1)

1-642: LGTM! Excellent test coverage for HTTP client.

The test suite is thorough and well-designed:

  • Comprehensive coverage of client functionality (construction, methods, headers, retry, timeout, cancellation)
  • Good edge case testing (retry exhaustion, 4xx no-retry, context cancellation)
  • Proper use of atomic counters for thread-safe request counting
  • Appropriate jitter tolerance in backoff tests (±20%)
  • APIError integration tests validate error propagation and details
  • Clear test structure with descriptive names
internal/criteria/evaluator.go (4)

577-582: Good fix: IsFieldNotFound now unwraps nested errors.

Using errors.As properly detects FieldNotFoundError wrapped inside other errors like EvaluationError, addressing the previous review concern.


57-64: LGTM! Efficient lazy CEL evaluator initialization.

Using sync.Once for lazy initialization avoids creating a CEL environment until needed and ensures thread-safe singleton behavior.


66-232: LGTM! Comprehensive field access and evaluation API.

The evaluator provides a well-designed API with:

  • Multiple access patterns (error-throwing, safe, with-defaults)
  • Detailed evaluation results with field values
  • Batch field extraction utilities
  • Clear distinction between safe and error-throwing variants

471-564: LGTM! Robust field traversal with clear error reporting.

The recursive field traversal implementation:

  • Handles maps, structs, and nested paths
  • Provides clear error messages with full path context
  • Supports case-insensitive struct field matching
  • Properly handles nil values with descriptive errors

@xueli181114 xueli181114 force-pushed the HYPERFLEET-47 branch 2 times, most recently from 05de19f to bbb45de Compare December 3, 2025 03:07
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 (2)
internal/broker_consumer/README.md (1)

46-63: Align NewSubscriber example with the real function signature.

The basic example shows subscriber, err := brokerconsumer.NewSubscriber(subscriptionID), but the integration tests use subscriber, subscriptionID, err := broker_consumer.NewSubscriber("") and rely on env-based subscription resolution. Please update the README to match the actual NewSubscriber signature/behavior so consumers don’t copy a non-compiling example.

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

227-247: ConfigMap namespace should be the cluster namespace name, not its phase.

For clusterConfigMap, metadata.namespace is set to {{ .resources.clusterNamespace.status.phase }}, which will resolve to values like "Active" rather than the actual namespace name (cluster-{{ .clusterId }}). That will either fail or place the ConfigMap into an unintended namespace.

You likely want to keep it in the cluster namespace:

-          namespace: "{{ .resources.clusterNamespace.status.phase }}"
+          namespace: "cluster-{{ .clusterId }}"
♻️ Duplicate comments (2)
test/integration/testutil/container.go (1)

157-170: Consider aligning retry-termination timeout with the 60s cleanup minimum

Both in StartContainer and StartSharedContainer, the “failed attempt” termination paths use a hard‑coded 30s timeout, while the rest of the code enforces a 60s minimum for cleanup to give containers time to stop gracefully. For consistency (and to reduce premature force‑cleanup / potential leaks), consider bumping these to 60s or wiring them to the same cleanup timeout policy:

- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)

and similarly for the shared‑container termination path.

Also applies to: 451-456

internal/hyperfleet_api/client.go (1)

55-63: Potential nil pointer dereference if option ordering is incorrect.

WithDefaultHeader accesses c.config.DefaultHeaders without checking if c.config is nil. While normal usage (with NewClient) ensures config is initialized first, direct usage of options could panic if WithConfig(nil) is passed before this option.

Given the functional options pattern always initializes config in NewClient before applying options, this is low risk. However, for defensive coding:

 func WithDefaultHeader(key, value string) ClientOption {
 	return func(c *httpClient) {
+		if c.config == nil {
+			return
+		}
 		if c.config.DefaultHeaders == nil {
 			c.config.DefaultHeaders = make(map[string]string)
 		}
 		c.config.DefaultHeaders[key] = value
 	}
 }

Based on past review comments, this was discussed and deemed acceptable since config is always initialized before options are applied.

🧹 Nitpick comments (20)
test/integration/broker_consumer/setup_test.go (1)

100-103: Good switch to t.Setenv for test-scoped environment variables

Using t.Setenv here is idiomatic and removes the need for manual env restoration in callers; this looks correct and safer for parallel tests. As a minor optional follow-up, you could also register the config file removal via t.Cleanup instead of returning a cleanup func, to make the helper fully self-contained and avoid relying on callers to remember invoking it, but that’s purely a style choice and not required for correctness.

scripts/run-integration-tests.sh (1)

43-43: Consider anchoring the grep pattern to avoid false positives.

The current pattern grep -q "hyperfleet-integration-test" is quite loose and could match unintended images (e.g., quay.io/org/hyperfleet-integration-test, my-hyperfleet-integration-test-custom). Since the script subsequently uses localhost/hyperfleet-integration-test:latest specifically (line 53), consider a more specific pattern that ensures you're detecting the intended local image.

Apply this diff to make the pattern more specific:

-    if ! $CONTAINER_RUNTIME images | grep -q "hyperfleet-integration-test"; then
+    if ! $CONTAINER_RUNTIME images | grep -qE "localhost/hyperfleet-integration-test|hyperfleet-integration-test:"; then

Alternatively, if you prefer to keep it simple and rely on the subsequent hardcoded image path being correct, the current approach is acceptable.

internal/k8s_client/README.md (1)

61-74: Consider adding section headers to separate the two DiscoverResources examples.

The two usage examples (label-based discovery and name-based discovery) could be more clearly separated with comment headers like // Example 1: Discover by label selector and // Example 2: Discover single resource by name to improve readability.

test/integration/k8s_client/helper_selector.go (1)

54-67: Prefer standard library strings.Contains over custom implementation.

The custom contains and containsSubstring functions reimplement functionality available in the standard library. Using strings.Contains(s, substr) would be simpler, more maintainable, and likely more performant.

Apply this diff to simplify:

+import "strings"
+
-// contains checks if a string contains a substring
 func contains(s, substr string) bool {
-	return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
-		(len(s) > len(substr) && containsSubstring(s, substr)))
-}
-
-func containsSubstring(s, substr string) bool {
-	for i := 0; i <= len(s)-len(substr); i++ {
-		if s[i:i+len(substr)] == substr {
-			return true
-		}
-	}
-	return false
+	return strings.Contains(s, substr)
 }
test/integration/k8s_client/helper_envtest_prebuilt.go (2)

40-47: Set TLS MinVersion for better security posture.

While InsecureSkipVerify is necessary for envtest's self-signed certificates, the TLS configuration should still specify a minimum version. Consider setting MinVersion: tls.VersionTLS12 to avoid using potentially vulnerable TLS 1.0/1.1.

Based on static analysis hints.

Apply this diff:

 	client := &http.Client{
 		Timeout: 5 * time.Second,
 		Transport: &http.Transport{
 			TLSClientConfig: &tls.Config{
+				MinVersion:         tls.VersionTLS12,
 				InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
 			},
 		},
 	}

149-155: Consider setting TLS MinVersion in rest.Config as well.

Similar to the HTTP client in waitForAPIServerReady, the rest.Config's TLS configuration should specify a minimum TLS version for consistency and security best practices.

Note: While rest.Config doesn't directly expose MinVersion in TLSClientConfig, you can create a custom TLSClientConfig if needed. However, since this is a test environment and Insecure: true already accepts self-signed certs, this is lower priority than the HTTP client fix.

test/integration/testutil/container.go (3)

177-205: Force‑cleanup helpers look solid; optional small refactor to avoid duplication

The t.Cleanup path that falls back to forceCleanupContainer, plus the forceCleanupContainerNoTest variant for TestMain, give a good last‑resort escape hatch when Terminate fails. The docker/podman usage via exec.Command and CombinedOutput is straightforward and safe.

If you want to trim duplication, you could extract the shared runtimes slice and common loop into a small internal helper (e.g. forceRemoveByRuntimes(containerID string, logf func(string, ...any))) used by both functions, but that’s optional.

Also applies to: 237-265, 497-515


340-382: SharedContainer API is clear; consider making Cleanup idempotent

The SharedContainer shape and GetEndpoint/Cleanup methods make reuse in TestMain straightforward, and the 60s timeout plus force‑cleanup fallback are aligned with the rest of the file.

If you expect Cleanup() might be called more than once (defers, defensive calls), you could make it explicitly idempotent by nil‑ing the container after a successful cleanup:

  if err := s.Container.Terminate(ctx); err != nil {
    // ...
  } else {
    println(fmt.Sprintf("✅ Shared %s container stopped and removed", s.Name))
+   s.Container = nil
  }

Purely a nicety; current behavior is acceptable.


384-495: Shared container startup: consider mirroring main StartContainer’s robustness

StartSharedContainer nicely encapsulates a shared container lifecycle (retries, port wiring, explicit cleanup in TestMain). Two improvements worth considering:

  1. Fallback when terminate during retries fails
    In the retry loop, if container.Terminate fails for a partially started container, there’s currently no force‑cleanup, so a broken attempt could leak a container. Mirroring StartContainer’s pattern would tighten this up:
- if container != nil {
-   terminateCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-   _ = container.Terminate(terminateCtx)
-   cancel()
- }
+ if container != nil {
+   terminateCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+   if termErr := container.Terminate(terminateCtx); termErr != nil {
+     if cid := container.GetContainerID(); cid != "" {
+       forceCleanupContainerNoTest(cid)
+     }
+   }
+   cancel()
+ }
  1. Reuse or document CleanupTimeout semantics
    ContainerConfig.CleanupTimeout is not used at all in the shared‑container path; callers might expect it to influence cleanup behavior here as well. Either wiring it into the retry‑termination and/or SharedContainer.Cleanup paths, or documenting that shared containers always use a fixed 60s cleanup window, would make the API contract clearer.

Both are refinements, not blockers.

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

26-27: Document the exists operator in the template header.

Later in this template you use operator: "exists" (e.g., for vpcId), but the “Supported operators” comment only lists equals/notEquals/in/notIn/contains/greaterThan/lessThan. It’d be clearer to include exists there as well.

-# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThan
+# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThan, exists
cmd/adapter/main.go (1)

182-222: API client creation helper is well-structured.

The createAPIClient function correctly:

  • Uses the options pattern for flexible configuration
  • Validates backoff strategy with explicit allowed values
  • Properly propagates errors with context

One minor observation: the timeout parsing and validation happens here, but ParseTimeout() is also called. Consider whether validation should be centralized.

 // Parse and set timeout using the accessor method
 timeout, err := apiConfig.ParseTimeout()
 if err != nil {
   return nil, fmt.Errorf("invalid timeout %q: %w", apiConfig.Timeout, err)
 }
-if timeout > 0 {
+if timeout > 0 {
   opts = append(opts, hyperfleet_api.WithTimeout(timeout))
+} else if apiConfig.Timeout != "" {
+  // Log warning if timeout was specified but parsed to zero
+  // This case might indicate a misconfiguration
 }
internal/criteria/evaluator_test.go (1)

414-468: evaluateExists tests cover key semantic cases.

The tests correctly validate that:

  • Empty strings/slices/maps return false
  • Non-nil values with content return true
  • Numbers always exist (including zero - though zero case isn't explicitly tested)

Consider adding a test case for zero numeric value to ensure evaluateExists treats 0 as existing:

{
  name:  "zero number",
  field: 0,
  want:  true, // or false, depending on intended semantics
},
internal/config_loader/validator_schema.go (1)

479-517: Legacy wrapper functions create validator instances per call.

These wrappers maintain backward compatibility but create a new SchemaValidator for each call. For single validations this is fine, but if called repeatedly in a loop, consider caching or refactoring callers to use the validator directly.

If performance becomes a concern, callers could be refactored to use SchemaValidator directly instead of these wrappers.

internal/config_loader/accessors.go (1)

21-24: Consider returning a copy to prevent external mutation.

BuiltinVariables() returns the internal slice directly, allowing callers to mutate it (e.g., append or element reassignment), which could affect all future callers.

 // BuiltinVariables returns the list of built-in variables always available in templates/CEL
 func BuiltinVariables() []string {
-	return builtinVariables
+	result := make([]string, len(builtinVariables))
+	copy(result, builtinVariables)
+	return result
 }
internal/config_loader/validator.go (1)

387-409: CEL type check failures are logged but don't fail validation.

The code logs type check errors at V(2) level but continues validation. This is intentional since DynType variables can't be fully type-checked, but consider documenting this behavior.

Consider adding a brief comment explaining why type check failures are non-fatal:

 	// Try to check the expression (type checking)
-	// Note: This may fail for dynamic variables, which is acceptable
+	// Note: Type checking may fail for DynType variables since actual types
+	// are unknown at validation time. Parse-only validation is sufficient to
+	// catch syntax errors; type errors will surface at runtime.
 	_, issues = v.celEnv.Check(ast)
internal/hyperfleet_api/client_test.go (2)

51-129: Consider verifying option effects, not just construction success.

The table-driven test validates that NewClient doesn't error with various options, but doesn't verify that the options are actually applied (e.g., timeout value, retry attempts). This is acceptable for smoke testing, but you may want to add assertions for critical options.


444-486: Coupling to internal implementation details.

This test directly instantiates the unexported httpClient struct to test calculateBackoff. While this provides good coverage of the backoff logic, it couples the test to internal implementation. Consider whether this behavior could be tested through the public API (e.g., by measuring actual delays during retries) or accept this as intentional white-box testing.

internal/config_loader/constants.go (1)

1-111: Missing FieldWhen constant.

The when field is used in the YAML configuration (e.g., test/testdata/adapter_config_valid.yaml lines 123, 202) but there's no corresponding FieldWhen constant defined here. Consider adding it for consistency.

 // Post config field names
 const (
 	FieldPostActions = "postActions"
+	FieldWhen        = "when"
 )
internal/criteria/evaluator.go (2)

110-199: LGTM!

The condition evaluation logic is comprehensive and handles all operators correctly. The detailed result collection in EvaluateConditionsWithResult is useful for debugging and inspection, even though it continues evaluating after the first failure.

Optional: Remove redundant type cast.

At line 184, the cast Operator(cond.Operator) is unnecessary since cond.Operator is already of type Operator (per the ConditionDef struct definition at line 293).

-		evalResult, err := e.EvaluateConditionWithResult(cond.Field, Operator(cond.Operator), cond.Value)
+		evalResult, err := e.EvaluateConditionWithResult(cond.Field, cond.Operator, cond.Value)

327-469: LGTM!

The operator evaluation helpers are comprehensive and handle edge cases well. The numeric conversion in toFloat64 covers all standard Go numeric types, and evaluateExists correctly handles various zero/empty values.

Optional: Clarify the map key checking logic.

At lines 360-371 in evaluateContains, the special handling for interface{} keyed maps (common in YAML/JSON) could benefit from a brief comment explaining why this case is checked separately after the assignability check.

 	// For maps - check if needle is a key in the map
 	if value.Kind() == reflect.Map {
 		needleVal := reflect.ValueOf(needle)
 		// Check if needle type is compatible with map key type
 		if needleVal.Type().AssignableTo(value.Type().Key()) {
 			return value.MapIndex(needleVal).IsValid(), nil
 		}
-		// Try string conversion for interface{} keyed maps (common in YAML/JSON)
+		// Special case: for interface{} keyed maps (common in YAML/JSON),
+		// the assignability check may not work as expected, so try MapIndex directly
 		if value.Type().Key().Kind() == reflect.Interface {
 			return value.MapIndex(needleVal).IsValid(), nil
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 782134c and bbb45de.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (54)
  • Makefile (2 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (5 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (4 hunks)
  • internal/broker_consumer/README.md (1 hunks)
  • internal/broker_consumer/consumer.go (3 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/constants.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (6 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • internal/k8s_client/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • pkg/logger/logger_test.go (2 hunks)
  • scripts/build-integration-image.sh (1 hunks)
  • scripts/run-integration-tests.sh (1 hunks)
  • scripts/start-envtest.sh (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/broker_consumer/setup_test.go (1 hunks)
  • test/integration/broker_consumer/testutil_publisher.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (32 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_selector.go (2 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (0 hunks)
  • test/integration/k8s_client/main_test.go (1 hunks)
  • test/integration/testutil/container.go (7 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • test/integration/k8s_client/helper_testcontainers_k3s.go
✅ Files skipped from review due to trivial changes (1)
  • internal/hyperfleet_api/README.md
🚧 Files skipped from review as they are similar to previous changes (21)
  • configs/broker-configmap-pubsub-template.yaml
  • internal/k8s_client/discovery.go
  • test/integration/broker_consumer/testutil_publisher.go
  • configs/templates/deployment.yaml
  • configs/templates/job.yaml
  • internal/criteria/README.md
  • test/integration/config-loader/loader_template_test.go
  • pkg/errors/api_error.go
  • internal/k8s_client/client.go
  • internal/k8s_client/types.go
  • configs/templates/cluster-status-payload.yaml
  • scripts/build-integration-image.sh
  • internal/broker_consumer/consumer.go
  • internal/k8s_client/client_test.go
  • test/testdata/templates/deployment.yaml
  • test/integration/config-loader/config_criteria_integration_test.go
  • scripts/start-envtest.sh
  • test/integration/k8s_client/main_test.go
  • pkg/logger/logger_test.go
  • Makefile
  • configs/README.md
🧰 Additional context used
🧬 Code graph analysis (14)
internal/config_loader/validator_test.go (4)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/config_loader/types.go (4)
  • AdapterConfig (6-11)
  • Resource (170-177)
  • DiscoveryConfig (180-184)
  • Payload (67-78)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
test/integration/broker_consumer/adapter_integration_test.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (66-70)
internal/config_loader/validator_schema.go (3)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Metadata (14-18)
  • APICall (107-115)
  • Resource (170-177)
  • SelectorConfig (187-189)
  • Precondition (98-104)
internal/config_loader/loader.go (2)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
internal/config_loader/constants.go (25)
  • FieldSpec (9-9)
  • FieldAdapter (15-15)
  • FieldVersion (26-26)
  • FieldParams (18-18)
  • FieldName (31-31)
  • FieldSource (32-32)
  • FieldPreconditions (19-19)
  • FieldAPICall (48-48)
  • FieldExpression (51-51)
  • FieldConditions (50-50)
  • FieldResources (20-20)
  • FieldManifest (78-78)
  • FieldDiscovery (81-81)
  • FieldNamespace (92-92)
  • FieldByName (93-93)
  • FieldBySelectors (94-94)
  • FieldLabelSelector (99-99)
  • FieldPost (21-21)
  • FieldPostActions (104-104)
  • FieldPayloads (41-41)
  • FieldMethod (56-56)
  • FieldURL (57-57)
  • FieldBuildRef (43-43)
  • FieldRef (86-86)
  • FieldRefs (87-87)
internal/criteria/evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • EvaluationError (100-104)
internal/criteria/evaluator.go (4)
  • NewEvaluator (48-55)
  • EvaluationResult (12-23)
  • IsFieldNotFound (584-587)
  • FieldNotFoundError (572-576)
internal/config_loader/constants.go (1)
  • FieldValue (72-72)
internal/hyperfleet_api/client.go (1)
pkg/errors/api_error.go (1)
  • NewAPIError (123-134)
internal/config_loader/accessors.go (2)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Parameter (51-58)
  • Resource (170-177)
  • Precondition (98-104)
  • PostAction (198-201)
  • HyperfleetAPIConfig (37-42)
internal/config_loader/constants.go (1)
  • FieldResources (20-20)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (6-11)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (461-473)
internal/config_loader/validator.go (1)
  • Validate (508-510)
test/integration/k8s_client/helper_envtest_prebuilt.go (2)
internal/k8s_client/client.go (2)
  • Client (20-23)
  • NewClientFromConfig (105-115)
test/integration/testutil/container.go (3)
  • ContainerConfig (20-51)
  • StartSharedContainer (403-495)
  • SharedContainer (342-354)
internal/config_loader/loader_test.go (5)
internal/config_loader/loader.go (4)
  • Load (88-111)
  • Parse (114-130)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
internal/config_loader/types.go (12)
  • Metadata (14-18)
  • HyperfleetAPIConfig (37-42)
  • AdapterConfig (6-11)
  • AdapterConfigSpec (21-29)
  • AdapterInfo (32-34)
  • PostConfig (192-195)
  • Payload (67-78)
  • Resource (170-177)
  • Parameter (51-58)
  • DiscoveryConfig (180-184)
  • SelectorConfig (187-189)
  • Condition (130-134)
internal/config_loader/accessors.go (1)
  • EnvHyperfleetAPIBaseURL (9-9)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (461-473)
  • IsSupportedAPIVersion (451-458)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/config_loader/types.go (3)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (2)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (368-383)
internal/criteria/evaluator.go (2)
  • ConditionDef (291-295)
  • NewEvaluator (48-55)
test/integration/k8s_client/client_integration_test.go (1)
test/integration/k8s_client/main_test.go (1)
  • GetSharedEnv (94-99)
internal/criteria/evaluator.go (3)
internal/config_loader/constants.go (2)
  • FieldValue (72-72)
  • FieldByName (93-93)
internal/criteria/types.go (12)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
  • OperatorEquals (12-12)
  • OperatorNotEquals (14-14)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
  • OperatorContains (20-20)
  • OperatorGreaterThan (22-22)
  • OperatorLessThan (24-24)
  • OperatorExists (26-26)
internal/criteria/cel_evaluator.go (4)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (368-383)
🪛 ast-grep (0.40.0)
test/integration/k8s_client/helper_envtest_prebuilt.go

[warning] 42-44: MinVersionis missing from this TLS configuration. By default, TLS 1.2 is currently used as the minimum when acting as a client, and TLS 1.0 when acting as a server. General purpose web applications should default to TLS 1.3 with all other protocols disabled. Only where it is known that a web server must support legacy clients with unsupported an insecure browsers (such as Internet Explorer 10), it may be necessary to enable TLS 1.0 to provide support. AddMinVersion: tls.VersionTLS13' to the TLS configuration to bump the minimum version to TLS 1.3.
Context: tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
}
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(missing-ssl-minversion-go)

🪛 markdownlint-cli2 (0.18.1)
internal/config_loader/README.md

75-75: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (87)
test/integration/k8s_client/client_integration_test.go (2)

16-30: LGTM! Clean approach to avoid test-only dependencies.

The local gvk struct effectively decouples the integration tests from internal test-only exports while maintaining clear, readable test code. The GroupVersionKinds are correctly defined for core v1 resources.


34-34: LGTM! Shared environment improves test performance.

The migration to GetSharedEnv(t) across all tests is a good optimization. Since each test creates uniquely-named resources (with timestamps), the shared environment approach won't cause test interference while significantly improving overall test suite performance.

test/integration/k8s_client/helper_selector.go (1)

23-24: LGTM! Proper interface conformance check.

The compile-time interface check ensures TestEnvPrebuilt correctly implements the TestEnv interface, catching any missing methods at build time rather than runtime.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

142-145: LGTM! Proper cleanup on failure.

The error handling correctly cleans up the shared container if API server readiness checks fail, preventing orphaned containers. This pattern is consistently applied throughout the setup function.


191-210: LGTM! Clean helper for namespace creation.

The createDefaultNamespaceNoTest function appropriately handles the edge case where the namespace might already exist, making the setup more robust. The non-fatal error handling in the caller (line 166-168) is also appropriate.

test/integration/testutil/container.go (2)

39-41: CleanupTimeout default and min enforcement are now consistent

The CleanupTimeout docs, default value, and cleanup-path min clamp are all aligned at 60s, which makes behavior predictable and avoids earlier confusion about an effective 60s minimum with a 30s documented default. This looks good.

Also applies to: 75-82, 106-107, 186-193


267-305: Leaked‑container cleanup via ancestor= filter is reasonable

CleanupLeakedContainers’s strategy of using docker|podman ps -a --filter ancestor=... and then rm -f on the resulting IDs is a pragmatic way to clean up from previous failed runs, and the behavior of silently skipping when CLIs are unavailable matches the comment.

No changes needed from my side here.

test/integration/broker_consumer/adapter_integration_test.go (1)

95-130: Improved smoke test flow removes timing flakiness.

The switch to a context-bound timeout plus publishAndWaitForMessage with a ticker is a solid way to handle subscriber readiness races without arbitrary sleeps. The buffered channel and non-blocking send in the handler also avoid goroutine leaks. Looks good.

Also applies to: 132-157

internal/criteria/types.go (1)

7-115: Operator and evaluation context abstractions look solid.

The operator enum, SupportedOperators helpers, EvaluationContext (with Merge/nested access), and EvaluationError (including Unwrap) form a clean, reusable foundation for the criteria and CEL layers. No issues from my side.

internal/criteria/cel_evaluator.go (1)

51-209: CEL evaluator and conversion helpers are coherent and well‑guarded.

The NewCELEvaluator setup, Evaluate/EvaluateSafe layering with categorized error reasons, and the ConditionToCEL/ConditionsToCEL helpers align with the documented semantics (errors captured in CELResult and handled by callers) and the accompanying tests. I don’t see further changes needed here.

Also applies to: 282-383

internal/criteria/cel_evaluator_test.go (1)

11-572: Comprehensive CEL evaluator test coverage.

These tests exercise the main success paths, nested-field access, has() behavior, error categorization, condition-to-CEL conversion, and formatting edge cases. The suite looks thorough and well-aligned with the implementation.

go.mod (1)

3-3: > Likely an incorrect or invalid review comment.

cmd/adapter/main.go (2)

62-72: Config loading and logging implementation looks good.

The configuration loading flow properly handles errors and provides informative logging. The use of config_loader.WithAdapterVersion(version) for version validation is a good practice.

Note: There's an existing discussion about log levels (Info vs Debug) that was deferred to post-MVP evaluation.


151-153: Placeholder suppression is acceptable for MVP.

The _ = adapterConfig and _ = apiClient assignments suppress unused variable warnings while the handler implementation is pending. The TODO comments clearly document the planned implementation steps.

internal/criteria/evaluator_test.go (5)

1-9: Comprehensive test file for the evaluator package.

Good test organization with clear separation of concerns. The use of testify/assert and testify/require is appropriate for the test assertions.


113-170: Good coverage of equals operator edge cases.

The test cases appropriately cover string comparisons, numeric comparisons, nil handling (both nil, one nil), providing thorough validation of the evaluateEquals function.


272-341: Thorough numeric comparison testing including zero boundary cases.

Good addition of zero-value test cases (lines 311-327) which validate boundary conditions for greaterThan operator. Similar coverage exists for lessThan (lines 382-398).


959-1018: Excellent null-safety test coverage.

The TestNullHandling function thoroughly validates behavior when traversing paths with null intermediate values. This is critical for safe field access in real-world JSON/YAML data structures.


673-707: toFloat64 test covers all Go numeric types.

Comprehensive coverage of int/uint variants and float types ensures robust numeric conversion. The error cases for string and bool are also validated.

internal/config_loader/validator_test.go (4)

12-276: Comprehensive operator validation tests.

The test suite covers:

  • Valid operators passing validation
  • Invalid operator rejection with clear error messages
  • Missing operator detection
  • Value requirements per operator type (equals needs value, exists doesn't)
  • List-type validation for in/notIn operators

This aligns well with the criteria.IsValidOperator function referenced in the relevant snippets.


278-423: Template variable validation tests are thorough.

Tests correctly validate:

  • Defined variables from params and capture
  • Undefined variable detection in URLs and manifests
  • Nested variable access from stored responses

The error messages include the undefined variable name, aiding debugging.


735-811: Good test for ManifestItems validation.

The tests properly validate that:

  • Valid ManifestItems with all required K8s fields pass
  • Invalid ManifestItems (missing apiVersion/kind/metadata) are rejected
  • Error messages include the specific index (manifestItems[1])

This ensures multi-manifest scenarios are correctly validated.


913-967: Payload validation unit tests are well-structured.

Direct testing of Payload.Validate() method covers all mutual exclusion scenarios:

  • Build only: valid
  • BuildRef only: valid
  • Both set: invalid
  • Neither set: invalid

This provides good unit-level coverage separate from integration via Parse().

internal/config_loader/validator_schema.go (5)

16-29: SchemaValidator design is clean and focused.

The struct encapsulates validation state with config and baseDir. The constructor pattern is straightforward.


31-52: ValidateStructure uses fail-fast pattern.

The sequential validator execution with early return on first error provides clear error messages. This is appropriate for configuration validation where fixing one error at a time is a reasonable workflow.


404-434: Path traversal protection is correctly implemented.

The resolvePath function properly:

  1. Gets absolute path for baseDir
  2. Resolves the target path (handling both absolute and relative)
  3. Uses filepath.Rel to check containment
  4. Rejects paths starting with ..

This addresses the security concern from the previous review.


336-383: File loading handles single and multiple refs appropriately.

The logic correctly:

  • Loads all referenced YAML files
  • For single ref: replaces manifest with content (backward compatible)
  • For multiple refs: stores in ManifestItems array
  • Provides clear error paths with indices

192-202: Selector validation enforces required labelSelector.

The validation correctly ensures that when bySelectors is used, at least one label selector must be defined. This prevents empty selector configurations that would match nothing.

internal/criteria/evaluator_scenarios_test.go (5)

10-89: Real-world scenario test provides good integration coverage.

The test simulates a realistic cluster evaluation scenario with:

  • Nested data structures matching API responses
  • Extracted fields as would be done by precondition capture
  • Combined condition evaluation

This validates the evaluator works correctly with production-like data shapes.


131-153: Good use of isolated context for parallel safety.

Creating localCtx and localEvaluator in the subtest prevents shared state issues when tests run in parallel. This is a good practice that should be followed in other subtests.


192-251: Map key containment tests validate important Kubernetes pattern.

Testing the contains operator on maps (checking for label/annotation key existence) is a common Kubernetes use case. The tests cover both map[string]interface{} and typed map[string]string scenarios.


253-284: Terminating cluster scenario correctly tests precondition failure.

The test validates that:

  • A "Terminating" phase cluster fails the in ["Provisioning", "Installing", "Ready"] check
  • The notIn ["Terminating", "Failed"] correctly blocks terminating clusters

The test names and assertions are now clear about expected behavior (addresses past review feedback).


286-365: Node count validation uses effective boundary testing.

The table-driven test covers:

  • Valid range (5 in 1-10)
  • Below minimum (0 < 1)
  • Above maximum (15 > 10)
  • At minimum boundary (1 == 1)
  • At maximum boundary (10 == 10)

The technique of using greaterThan minNodes-1 and lessThan maxNodes+1 to simulate >= and <= is a reasonable workaround given the available operators.

internal/config_loader/accessors.go (5)

37-82: LGTM - well-structured aggregation of defined variables.

The GetDefinedVariables() method correctly aggregates variables from multiple sources (built-ins, params, precondition captures, payloads, and resources) with proper nil safety. The use of FieldResources constant for resource alias prefixing maintains consistency.


84-172: LGTM - accessor methods are nil-safe and idiomatic.

All lookup accessors (GetParamByName, GetRequiredParams, GetResourceByName, GetPreconditionByName, GetPostActionByName, ParamNames, ResourceNames) properly check for nil receivers and return pointers to the original slice elements where applicable, avoiding unnecessary copies while maintaining safety.


178-198: LGTM - clean timeout parsing with proper nil handling.

ParseTimeout() correctly returns (0, nil) for empty timeout, letting the caller decide on defaults. GetBaseURL() is appropriately simple and the doc comment clearly explains that environment fallback is handled elsewhere.


204-262: LGTM - robust manifest accessors with proper normalization.

The manifest accessors correctly handle both ref (single) and refs (array) patterns. UnmarshalManifest provides clear error messaging with type information when the manifest cannot be converted.


272-290: LGTM - handles yaml.v2/yaml.v3 compatibility well.

The normalizeToStringKeyMap helper correctly handles both map[string]interface{} (yaml.v3) and map[interface{}]interface{} (yaml.v2) types, with a fallback to fmt.Sprintf for non-string keys.

internal/config_loader/loader_test.go (5)

13-71: LGTM - comprehensive test for config loading.

The test correctly creates a temporary config file, loads it, and verifies key fields. Good use of t.TempDir() for cleanup and proper file permission (0644).


80-174: LGTM - well-structured table-driven tests for Parse validation.

The tests cover essential validation scenarios: valid minimal config, missing apiVersion, missing kind, missing metadata.name, and missing adapter.version. Each test case includes expected error message verification.


619-649: LGTM - thorough test for GetBaseURL behavior.

Excellent test that explicitly verifies the accessor does NOT perform environment variable fallback (which is NewClient's responsibility), with proper env var cleanup in the defer block.


1040-1235: LGTM - comprehensive resource discovery validation tests.

The test suite covers valid scenarios (manifest.ref with bySelectors/byName, inline manifest, refs array) and invalid scenarios (missing discovery, missing namespace, missing byName/bySelectors, empty selectors). Good edge case coverage.


1358-1371: LGTM - validates mutual exclusivity of value and values.

Good test ensuring that specifying both value and values keys in a condition produces an appropriate error.

internal/config_loader/validator.go (7)

19-51: LGTM - clean validation error types with proper formatting.

ValidationError and ValidationErrors provide structured error reporting. The Error() method on ValidationErrors produces well-formatted multi-line output showing error count and all individual errors.


76-97: LGTM - well-organized validation orchestration.

The Validate() method follows a clear pattern: initialize context, run all validators, and aggregate errors. The early nil check prevents panics, and error aggregation allows collecting all issues in one pass.


144-165: LGTM - appropriate operator-value validation logic.

The validation correctly handles the special case where exists operator doesn't require a value, and enforces list values for in/notIn operators using reflection.


180-181: Regex allows pipes but may miss some template edge cases.

The regex \{\{\s*\.([a-zA-Z_][a-zA-Z0-9_\.]*)\s*(?:\|[^}]*)?\}\} handles basic template variables with optional pipe filters. However, it won't match templates with multiple nested braces or complex filter chains.

This is likely sufficient for the expected use cases. Verify the template syntax supported in your adapter configs aligns with this pattern.


260-290: LGTM - correct nested variable resolution for template validation.

The isVariableDefined method properly handles root variable lookups and special-cases resources.<alias> paths. This enables correct validation of expressions like resources.clusterNamespace.metadata.namespace.


318-358: LGTM - dynamic CEL environment with proper variable registration.

The CEL environment is correctly initialized with cel.OptionalTypes() for optional chaining and cel.DynType for all variables (since types are unknown at validation time). The deduplication of root variables prevents duplicate declarations.


468-499: LGTM - thorough K8s manifest validation.

The validateK8sManifest method checks for required Kubernetes fields (apiVersion, kind, metadata) and validates that metadata.name is present. Empty string checks for apiVersion and kind prevent subtle misconfigurations.

internal/config_loader/loader.go (5)

16-39: LGTM - well-organized constants and validation data.

The API version constant, expected kind, and HTTP method maps provide a single source of truth for validation. Having both a map (for O(1) lookup) and a list (for error messages) is pragmatic.


45-73: LGTM - clean functional options implementation.

The LoaderOption pattern with loaderConfig is idiomatic Go. Options are simple and focused on single responsibilities.


88-111: LGTM - robust file loading with automatic base directory resolution.

The Load function correctly handles empty paths by falling back to the environment variable, derives the absolute path for base directory calculation, and prepends WithBaseDir to allow user override.


113-130: LGTM - clean separation between parsing and validation.

Parse correctly initializes config, applies options, unmarshals YAML, and delegates to the validation pipeline. Error wrapping provides clear context.


140-187: LGTM - well-structured validation pipeline.

The pipeline correctly sequences structural validation before file reference handling and semantic validation. The conditional checks (cfg.adapterVersion, cfg.baseDir, !cfg.skipSemanticValidation) appropriately gate optional validation steps.

internal/hyperfleet_api/client.go (6)

30-34: LGTM - simplified client struct.

The data race issue with *rand.Rand was addressed by removing it from the struct. The client now uses the package-level rand.Float64() which is concurrency-safe.


117-145: LGTM - proper client initialization with environment fallback.

NewClient correctly applies options, falls back to environment variable for base URL if not set via options, and validates that base URL is configured. The error message clearly indicates both configuration methods.


166-246: LGTM - robust retry implementation with proper error handling.

The retry loop correctly:

  • Normalizes retryAttempts to minimum 1 (addressing the past review comment)
  • Checks context before each attempt
  • Distinguishes between success, non-retryable errors, and retryable failures
  • Avoids sleeping after the last attempt
  • Returns comprehensive APIError with all relevant details

248-262: LGTM - clean URL resolution logic.

resolveURL correctly handles absolute URLs (with scheme) and prepends base URL for relative paths. The trailing slash handling on baseURL and leading slash handling on URL prevents double-slash issues.


331-363: LGTM - well-implemented backoff calculation with jitter.

The backoff calculation supports exponential, linear, and constant strategies. The ±10% jitter using package-level rand.Float64() (concurrency-safe) prevents thundering herd. Max delay cap ensures bounded wait times.


369-435: LGTM - consistent convenience method implementations.

All HTTP verb methods (Get, Post, Put, Patch, Delete) follow a consistent pattern: create request, apply options, delegate to Do. The BaseURL() accessor provides read-only access to the configured base URL.

internal/hyperfleet_api/client_test.go (6)

1-28: LGTM! Well-structured client construction tests.

The test suite begins with solid coverage of NewClient construction scenarios, including the base case and missing base URL validation.


131-239: LGTM! Comprehensive HTTP method and header tests.

Good coverage of GET, POST methods and header propagation. The tests properly validate both the request sent to the server and the response received by the client.


241-356: LGTM! Thorough retry logic test coverage.

Excellent coverage of retry mechanics including successful retry, exhausted retries, and no-retry-on-4xx behavior. The use of atomic counters ensures thread-safe request counting.


358-442: LGTM! Good timeout/cancellation and response helper coverage.

The timeout tests use appropriate timing ratios, and the response helper tests thoroughly cover status code classification including edge cases like 408 (Request Timeout) and 429 (Too Many Requests) being retryable.


488-561: LGTM! Complete HTTP method coverage.

Clean tests for PUT, PATCH, and DELETE methods, validating correct HTTP method usage and response handling.


563-721: LGTM! Comprehensive APIError test coverage.

Excellent coverage of the APIError type including construction, helper methods, error unwrapping with errors.Is/As semantics, and integration testing with the retry mechanism. The wrapped error test at lines 658-664 properly validates Go error wrapping patterns.

internal/hyperfleet_api/types.go (5)

1-32: LGTM! Clean type definitions and sensible defaults.

Well-structured backoff strategy constants and reasonable default values. The 10-second timeout, 3 retry attempts, and exponential backoff with 1s base / 30s max delay are production-appropriate defaults.


56-66: LGTM! Proper initialization of DefaultHeaders.

Good practice initializing DefaultHeaders to an empty map to prevent nil map panics when adding headers later.


72-152: LGTM! Well-designed request options pattern.

Good use of the functional options pattern. The pointer types for RetryAttempts and RetryBackoff correctly distinguish between "not set" and "set to zero/default". The nil-check on headers map initialization prevents panics.


154-209: LGTM! Correct response helper implementations.

The status code range checks follow standard HTTP semantics. The IsRetryable method correctly identifies server errors and the special 4xx cases (408 Request Timeout, 429 Too Many Requests) that are appropriate for retry.


215-237: LGTM! Clean Client interface design.

The interface follows Go conventions with context as the first parameter. The BaseURL() accessor is useful for callers that need to construct URLs relative to the base.

test/testdata/adapter_config_valid.yaml (4)

1-50: LGTM! Well-structured test configuration.

The configuration file provides good test coverage of the adapter config schema including parameters, HyperFleet API settings, and event-sourced values. The retry configuration (lines 18-19) aligns with the new BackoffStrategy type.


51-87: LGTM! Comprehensive precondition examples.

Good coverage of both structured conditions (with operators like in and exists) and CEL expressions. The API call configuration properly demonstrates timeout and retry settings.


88-137: LGTM! Comprehensive resource configuration examples.

Good coverage of various resource patterns including:

  • Namespace with discovery by label selectors
  • ConfigMap with conditional creation (when clause)
  • External template reference

138-205: LGTM! Complete post-processing configuration.

The payload build structure and post actions demonstrate the full post-processing workflow. The CEL expressions and template interpolation are well-formed.

internal/config_loader/constants.go (1)

63-74: Intentional duplicate values noted.

FieldHeaderValue and FieldValue both have the value "value" but serve different semantic purposes (header values vs. condition values). This is acceptable for clarity, but could be consolidated if the contexts are always distinguishable.

internal/config_loader/types.go (5)

1-59: LGTM! Well-structured configuration types.

The type hierarchy cleanly maps to the YAML configuration structure. Good use of omitempty tags for optional fields and interface{} for flexible schema elements like Default.


60-95: LGTM! Proper mutual exclusivity validation for Build/BuildRef.

The Validate() method correctly enforces that exactly one of Build or BuildRef must be set. The documentation clearly explains the constraint.


97-127: LGTM! Flexible precondition structure.

The Precondition type supports both API-call-based preconditions with data capture and pure CEL expression preconditions. The string type for Timeout allows flexible duration parsing (e.g., "10s", "1m").


129-167: LGTM! Clean dual-key support for conditions.

The custom UnmarshalYAML elegantly handles both value and values YAML keys while properly rejecting configurations that specify both. This provides good UX flexibility for config authors.


169-206: Missing When field on PostAction and Resource types.

Based on the test YAML (lines 123-127 and 202-205 of adapter_config_valid.yaml), both Resource and PostAction support a when clause for conditional execution. However, these fields are not defined in the struct types shown here.

internal/criteria/evaluator.go (5)

11-55: LGTM!

The type definitions are well-structured with clear field documentation. The lazy initialization pattern using sync.Once for the CEL evaluator is efficient and thread-safe, avoiding redundant environment creation for repeated evaluations.


57-108: LGTM!

The field access methods provide appropriate variations for different error-handling needs (strict, default, safe). The null-safety semantics are consistent and well-documented.


201-287: LGTM!

The field extraction methods offer appropriate flexibility for different use cases. The CEL integration cleanly delegates to the lazily-initialized evaluator with proper error handling.


289-325: LGTM!

The separation of unmarshaling types (ConditionDefJSON) from runtime types (ConditionDef) is good design. The evaluateEquals implementation correctly handles nil cases and uses reflect.DeepEqual for comprehensive comparison.


471-587: LGTM!

The field traversal implementation is robust and handles various data structures (maps, structs, YAML-style map[interface{}]interface{}). The case-insensitive fallback for struct fields provides good usability. The IsFieldNotFound helper correctly uses errors.As to detect wrapped FieldNotFoundError instances, addressing the concern from previous reviews.

@xueli181114 xueli181114 force-pushed the HYPERFLEET-47 branch 2 times, most recently from 95bdc0e to 6f0fed4 Compare December 3, 2025 03:56
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

Caution

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

⚠️ Outside diff range comments (2)
configs/adapter-config-template.yaml (2)

227-247: Fix clusterConfigMap namespace templating (currently uses status.phase)

For the clusterConfigMap resource, the manifest’s namespace is:

metadata:
  name: "cluster-config-{{ .clusterId }}"
  namespace: "{{ .resources.clusterNamespace.status.phase }}"

Using resources.clusterNamespace.status.phase here will generate namespaces like "Active" rather than "cluster-<id>", and will not match the discovery config below, which looks for the ConfigMap in cluster-{{ .clusterId }}. This will cause the template to create the ConfigMap in the wrong (or non-existent) namespace.

You likely want this to match the namespace used elsewhere:

-          namespace: "{{ .resources.clusterNamespace.status.phase }}"
+          namespace: "cluster-{{ .clusterId }}"

That keeps the manifest and discovery aligned and matches the pattern used in your test config.


26-27: Update supported-operators comment to include exists

The header comment currently says:

# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThan

But the criteria layer now supports and actively uses an exists operator (see the vpcId condition in this same file and OperatorExists in internal/criteria/types.go). To avoid confusion for adapter authors, it would be good to update the comment:

-# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThan
+# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThan, exists
♻️ Duplicate comments (3)
test/integration/testutil/container.go (1)

157-170: Align partial-start termination timeouts with the 60s cleanup minimum

In StartContainer and StartSharedContainer, containers created during a failed startup attempt are terminated with a 30s timeout:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
...
terminateCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)

Everywhere else, including the registered cleanup, you enforce a 60s minimum for graceful shutdown, and the comments describe 60s as the effective minimum. Using a shorter 30s window here is inconsistent and slightly increases the chance that you’ll hit the force-cleanup path unnecessarily for slow-shutdown images.

Consider reusing the same 60s window (or config.CleanupTimeout with the same minimum) for these termination contexts:

-ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
...
-terminateCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+terminateCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)

This keeps all shutdown paths on the same budget and matches the documented behavior.

Also applies to: 433-457

internal/criteria/cel_evaluator.go (1)

307-318: Acknowledged: exists operator behavior for nested paths returns error on missing ancestors.

Per previous review discussion, the current design intentionally uses has(a.b.c) for nested paths, which will error if intermediate paths don't exist. This error is captured by EvaluateSafe and the caller can treat it as false. The semantic distinction (error vs. clean false for existence checks) is a design trade-off that's been discussed.

The current behavior is documented in the function comment (lines 283-285). Consider adding explicit documentation noting that callers should treat errors as false for existence checks on nested paths.

internal/criteria/evaluator.go (1)

471-587: Nested field traversal and IsFieldNotFound behavior look solid

The combination of getFieldRecursive, getFieldValue, getStructField, and FieldNotFoundError gives clear error signaling for missing or inaccessible fields, and IsFieldNotFound now correctly uses errors.As to detect wrapped FieldNotFoundError instances. This should integrate cleanly with higher‑level error handling like EvaluateSafe.

🧹 Nitpick comments (18)
pkg/logger/logger_test.go (1)

185-193: Strengthened context-value assertions look good; consider avoiding duplicate errors when val is nil

The added checks correctly verify both presence and correctness of the context value, and using %v in the messages is appropriate for mixed types.

To avoid emitting two error messages when val is nil, you could turn the second if into an else if:

-				if val == nil {
-					t.Errorf("Expected context value for key %v, got nil", tt.expectedKey)
-				}
-				if val != tt.expectedValue {
-					t.Errorf("Expected context value %v, got %v", tt.expectedValue, val)
-				}
+				if val == nil {
+					t.Errorf("Expected context value for key %v, got nil", tt.expectedKey)
+				} else if val != tt.expectedValue {
+					t.Errorf("Expected context value %v, got %v", tt.expectedValue, val)
+				}
test/integration/k8s_client/helper_selector.go (1)

54-57: Simplify contains implementation for readability

The current boolean expression is correct but non-obvious to read and reason about. You can keep the same behavior with a more straightforward structure:

 func contains(s, substr string) bool {
-	return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
-		(len(s) > len(substr) && containsSubstring(s, substr)))
+	if substr == "" {
+		return true
+	}
+	if len(substr) > len(s) {
+		return false
+	}
+	if s == substr {
+		return true
+	}
+	return containsSubstring(s, substr)
 }

This keeps the existing semantics (including substr == "" returning true) but is easier to maintain and less error-prone for future modifications.

test/integration/broker_consumer/adapter_integration_test.go (2)

95-97: 60s timeout is fine; consider extracting a shared constant

Using a 60s context deadline here is reasonable for emulator-based integration tests, but it’s a magic number duplicated elsewhere. Consider extracting a shared test-level constant (e.g., const adapterTestTimeout = 60 * time.Second) so future tweaks apply consistently across tests.


124-158: Polling helper removes flakiness and looks correct

The new publishAndWaitForMessage helper is a solid improvement over fixed sleeps: it actively retries publishes until the handler signals via messageReceived or the context times out, which should significantly reduce race-driven flakes. The channel buffering + non-blocking handler send also avoids deadlocks.

If you want a bit more diagnosability on failures, you could log ctx.Err() in the timeout case before returning false, but the current behavior is functionally sound.

configs/templates/job.yaml (1)

1-28: Optionally parameterize the Job image with imageTag

The template looks correct and matches the discovery labels expected by validationJob. For consistency with configs/adapter-config-template.yaml (which defines an imageTag param) and the deployment template, you could also drive the validator image tag from that param:

-        - name: validator
-          image: "quay.io/hyperfleet/validator:v1.0.0"
+        - name: validator
+          image: "quay.io/hyperfleet/validator:{{ .imageTag }}"

This keeps all adapter images aligned on the same tag configuration.

internal/criteria/types.go (1)

60-97: Defensively initialize EvaluationContext.Data in Set/Merge

Right now Set and Merge assume c.Data is non-nil:

func (c *EvaluationContext) Set(key string, value interface{}) {
	c.Data[key] = value
}
...
for k, v := range other.Data {
	c.Data[k] = v
}

If a caller constructs EvaluationContext without using NewEvaluationContext (e.g. var c EvaluationContext or &EvaluationContext{}), these writes will panic on a nil map. You can make this type safer with lazy initialization:

 func (c *EvaluationContext) Set(key string, value interface{}) {
-	c.Data[key] = value
+	if c.Data == nil {
+		c.Data = make(map[string]interface{})
+	}
+	c.Data[key] = value
 }
 
 func (c *EvaluationContext) Merge(other *EvaluationContext) {
 	if other == nil {
 		return
 	}
-	for k, v := range other.Data {
-		c.Data[k] = v
-	}
+	if len(other.Data) == 0 {
+		return
+	}
+	if c.Data == nil {
+		c.Data = make(map[string]interface{}, len(other.Data))
+	}
+	for k, v := range other.Data {
+		c.Data[k] = v
+	}
 }

This preserves current behavior when NewEvaluationContext is used while avoiding panics if a zero-value context slips in.

test/integration/testutil/container.go (1)

237-265: Optionally deduplicate docker/podman force-cleanup logic

Both forceCleanupContainer and forceCleanupContainerNoTest implement nearly the same docker/podman rm -f loop:

runtimes := []string{"docker", "podman"}
for _, runtime := range runtimes {
    rmCmd := exec.Command(runtime, "rm", "-f", containerID)
    ...
}

If you want to slim this down, you could factor the common logic into an internal helper that accepts a logging function:

type logFn func(msg string)

func forceRemoveByID(id string, log logFn) {
    if id == "" {
        return
    }
    for _, runtime := range []string{"docker", "podman"} {
        rmCmd := exec.Command(runtime, "rm", "-f", id)
        if output, err := rmCmd.CombinedOutput(); err == nil {
            log(fmt.Sprintf("Force-removed container %s using %s", id, runtime))
            return
        } else {
            log(fmt.Sprintf("Failed to force-remove with %s: %v (output: %s)", runtime, err, string(output)))
        }
    }
}

Then forceCleanupContainer and forceCleanupContainerNoTest can just wrap log with t.Logf or println as appropriate.

This is non-essential, but it would reduce duplication and keep the cleanup semantics in one place.

Also applies to: 497-515

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

16-28: Consider making getProjectRoot less dependent on substring matching

getProjectRoot finds the project root by doing a strings.Index(filename, projectName) and slicing:

idx := strings.Index(filename, projectName)
if idx == -1 {
    panic("could not find project root: " + projectName + " not found in path")
}
return filename[:idx+len(projectName)]

This works as long as no parent directory name also contains projectName as a substring (e.g., /home/me/hyperfleet-adapter-sandbox/.../hyperfleet-adapter/...). To make it a bit more robust, you could split the path and look for a segment equal to projectName, or just rely on relative paths from the module root (since go test ./... runs within the module):

// Simple alternative if tests always run from the module root:
func getProjectRoot() string {
    wd, err := os.Getwd()
    if err != nil {
        panic(err)
    }
    return wd
}

Not critical, but it would reduce the risk of brittle behavior in unusual workspace layouts.


54-63: Optionally assert the new imageTag param in the template integration test

TestLoadTemplateConfig does a good job of smoking the template structure (API version, preconditions, resources, basic post config). Since you’ve introduced a new global param:

- name: "imageTag"
  source: "env.IMAGE_TAG"
  type: "string"
  default: "v1.0.0"
  description: "Tag for container images"
  required: false

you could extend the test to assert its presence as well, so future edits to the template can’t accidentally drop/misname it:

imageTagParam := config.GetParamByName("imageTag")
require.NotNil(t, imageTagParam, "imageTag parameter should exist")
assert.Equal(t, "env.IMAGE_TAG", imageTagParam.Source)
assert.Equal(t, "string", imageTagParam.Type)
assert.False(t, imageTagParam.Required)

This is purely for stronger integration coverage and is safe to defer.

Also applies to: 97-111

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

38-77: Consider adding MinVersion to TLS config.

The TLS configuration uses InsecureSkipVerify (appropriately, for envtest self-signed certificates), but doesn't specify a MinVersion. While this is a test-only scenario, setting a minimum TLS version is still recommended for consistency with security best practices.

Apply this diff:

 Transport: &http.Transport{
 	TLSClientConfig: &tls.Config{
-		InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
+		InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
+		MinVersion:         tls.VersionTLS12,
 	},
 },

Based on static analysis hints.

internal/config_loader/accessors.go (1)

21-24: Consider returning a copy of the slice to prevent external mutation.

BuiltinVariables() returns the internal builtinVariables slice directly. If a caller modifies the returned slice, it will affect all subsequent calls.

 func BuiltinVariables() []string {
-	return builtinVariables
+	result := make([]string, len(builtinVariables))
+	copy(result, builtinVariables)
+	return result
 }
internal/hyperfleet_api/types.go (1)

189-201: Consider adding 503 Service Unavailable explicitly to documentation.

While 503 is covered by IsServerError(), it's one of the most common retryable status codes. The comment lists specific codes (408, 429) but groups all 5xx together. Consider making 502/503/504 (gateway errors) explicit in the documentation since they're particularly relevant for API retries.

 // IsRetryable returns true if the request should be retried based on status code.
 // Retryable codes:
 //   - All 5xx server errors (500, 502, 503, 504, etc.)
+//     (especially 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout)
 //   - 408 Request Timeout
 //   - 429 Too Many Requests
internal/hyperfleet_api/client.go (2)

276-307: Log the resolved URL instead of (or in addition to) the raw request URL

doRequest logs req.URL rather than the resolvedURL actually sent to the server, which can make debugging base‑URL issues harder. Consider swapping to resolvedURL (or logging both) so logs always show the exact URL used on the wire.

-	// Execute request
-	glog.V(2).Infof("HyperFleet API request: %s %s", req.Method, req.URL)
+	// Execute request (log the resolved URL actually used on the wire)
+	glog.V(2).Infof("HyperFleet API request: %s %s", req.Method, resolvedURL)

266-275: Clarify/guard timeout semantics for zero values and custom HTTP clients

doRequest always wraps the parent context with context.WithTimeout using c.config.Timeout (or req.Timeout), and NewClient also sets http.Client.Timeout = c.config.Timeout when constructing the default client. When Timeout is zero or a custom http.Client is injected via WithHTTPClient, this can yield surprising behavior (immediate cancellation for Timeout <= 0, or WithTimeout having no effect on a caller‑supplied client).

Consider either:

  • Treating Timeout <= 0 as “no per‑request deadline” (skip WithTimeout and rely on parent context / client timeout), and/or
  • Documenting that WithTimeout only applies when you don’t also pass WithHTTPClient.
internal/hyperfleet_api/client_test.go (2)

16-17: Use a less ambiguous alias than errors for the API error package

This test file aliases github.com/openshift-hyperfleet/hyperfleet-adapter/pkg/errors as errors, which can easily be confused with the standard library errors package (and differs from the apierrors alias in production code). Consider renaming the alias here (for example, apierrors) for consistency and to avoid confusion.


30-55: Simplify environment handling in TestNewClientMissingBaseURL

The combination of os.LookupEnv, t.Setenv, os.Unsetenv, and an extra t.Cleanup is more complex than needed and duplicates restoration logic that t.Setenv already provides. Since os.Getenv returns "" for both unset and empty values, you can just use t.Setenv(EnvBaseURL, "") and drop the manual unset/cleanup.

-func TestNewClientMissingBaseURL(t *testing.T) {
-	// Unset env var to ensure we get an error
-	// Note: t.Setenv automatically restores original value, but we need to unset first
-	originalEnv, wasSet := os.LookupEnv(EnvBaseURL)
-	if wasSet {
-		t.Setenv(EnvBaseURL, "") // This will restore originalEnv when test ends
-	}
-	// Clear the env var for this test
-	if err := os.Unsetenv(EnvBaseURL); err != nil {
-		t.Fatalf("Failed to unset env var: %v", err)
-	}
-	// Restore after test if it was set
-	if wasSet {
-		t.Cleanup(func() {
-			_ = os.Setenv(EnvBaseURL, originalEnv)
-		})
-	}
+func TestNewClientMissingBaseURL(t *testing.T) {
+	// Ensure base URL env var is empty for this test; t.Setenv restores it automatically.
+	t.Setenv(EnvBaseURL, "")
internal/config_loader/types.go (1)

80-95: Include payload name in validation error messages for easier debugging

Payload.Validate enforces the Build vs BuildRef exclusivity, but the returned errors don’t indicate which payload entry failed. When multiple payloads are present, that makes config troubleshooting harder.

Consider including p.Name in the error messages:

	if hasBuild && hasBuildRef {
-		return fmt.Errorf("build and buildRef are mutually exclusive; set only one")
+		return fmt.Errorf("payload %q: build and buildRef are mutually exclusive; set only one", p.Name)
 	}
 	if !hasBuild && !hasBuildRef {
-		return fmt.Errorf("either build or buildRef must be set")
+		return fmt.Errorf("payload %q: either build or buildRef must be set", p.Name)
 	}
internal/criteria/evaluator.go (1)

110-163: Wrap evaluation helper errors in EvaluationError for consistent diagnostics

EvaluateConditionWithResult wraps field‑lookup failures and unsupported operators in EvaluationError, but returns raw errors from helpers like evaluateGreaterThan, evaluateLessThan, evaluateIn, etc. Callers then see conversion/compare errors without field/operator context.

For consistency and easier debugging, consider wrapping non‑nil err from the helper section in an EvaluationError (including Field, Operator, and ExpectedValue), similar to the GetNestedField/unsupported‑operator paths.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbb45de and 95bdc0e.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (54)
  • Makefile (3 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (5 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (6 hunks)
  • internal/broker_consumer/README.md (1 hunks)
  • internal/broker_consumer/consumer.go (3 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/constants.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (6 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • internal/k8s_client/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • pkg/logger/logger_test.go (2 hunks)
  • scripts/build-integration-image.sh (1 hunks)
  • scripts/run-integration-tests.sh (1 hunks)
  • scripts/start-envtest.sh (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/broker_consumer/setup_test.go (1 hunks)
  • test/integration/broker_consumer/testutil_publisher.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (32 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_selector.go (2 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (0 hunks)
  • test/integration/k8s_client/main_test.go (1 hunks)
  • test/integration/testutil/container.go (7 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • test/integration/k8s_client/helper_testcontainers_k3s.go
✅ Files skipped from review due to trivial changes (1)
  • internal/criteria/README.md
🚧 Files skipped from review as they are similar to previous changes (21)
  • internal/config_loader/README.md
  • internal/broker_consumer/consumer.go
  • test/integration/broker_consumer/setup_test.go
  • configs/templates/deployment.yaml
  • configs/templates/cluster-status-payload.yaml
  • configs/README.md
  • internal/k8s_client/client_test.go
  • internal/k8s_client/README.md
  • scripts/start-envtest.sh
  • cmd/adapter/main.go
  • scripts/run-integration-tests.sh
  • pkg/errors/api_error.go
  • internal/k8s_client/types.go
  • configs/broker-configmap-pubsub-template.yaml
  • internal/k8s_client/client.go
  • internal/k8s_client/discovery.go
  • internal/config_loader/constants.go
  • internal/criteria/evaluator_scenarios_test.go
  • test/testdata/templates/deployment.yaml
  • test/integration/config-loader/config_criteria_integration_test.go
  • test/integration/broker_consumer/testutil_publisher.go
🧰 Additional context used
🧬 Code graph analysis (13)
test/integration/k8s_client/client_integration_test.go (1)
test/integration/k8s_client/main_test.go (1)
  • GetSharedEnv (94-99)
internal/config_loader/validator_schema.go (5)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Metadata (14-18)
  • APICall (107-115)
  • Resource (170-177)
  • SelectorConfig (187-189)
  • Precondition (98-104)
internal/config_loader/loader.go (4)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
  • ValidHTTPMethodsList (39-39)
internal/config_loader/constants.go (25)
  • FieldSpec (9-9)
  • FieldAdapter (15-15)
  • FieldVersion (26-26)
  • FieldParams (18-18)
  • FieldName (31-31)
  • FieldSource (32-32)
  • FieldPreconditions (19-19)
  • FieldAPICall (48-48)
  • FieldExpression (51-51)
  • FieldConditions (50-50)
  • FieldResources (20-20)
  • FieldManifest (78-78)
  • FieldDiscovery (81-81)
  • FieldNamespace (92-92)
  • FieldByName (93-93)
  • FieldBySelectors (94-94)
  • FieldLabelSelector (99-99)
  • FieldPost (21-21)
  • FieldPostActions (104-104)
  • FieldPayloads (41-41)
  • FieldMethod (56-56)
  • FieldURL (57-57)
  • FieldBuildRef (43-43)
  • FieldRef (86-86)
  • FieldRefs (87-87)
internal/k8s_client/discovery.go (1)
  • Discovery (14-29)
internal/config_loader/validator.go (1)
  • Validate (508-510)
test/integration/k8s_client/helper_envtest_prebuilt.go (2)
internal/k8s_client/client.go (2)
  • Client (20-23)
  • NewClientFromConfig (105-115)
test/integration/testutil/container.go (3)
  • ContainerConfig (20-51)
  • StartSharedContainer (403-495)
  • SharedContainer (342-354)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (6-11)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (461-473)
internal/config_loader/validator.go (1)
  • Validate (508-510)
test/integration/k8s_client/main_test.go (2)
test/integration/k8s_client/helper_selector.go (1)
  • TestEnv (15-21)
test/integration/k8s_client/helper_envtest_prebuilt.go (1)
  • TestEnvPrebuilt (80-86)
internal/config_loader/accessors.go (2)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Parameter (51-58)
  • Resource (170-177)
  • Precondition (98-104)
  • PostAction (198-201)
  • HyperfleetAPIConfig (37-42)
internal/config_loader/constants.go (1)
  • FieldResources (20-20)
test/integration/config-loader/loader_template_test.go (4)
internal/config_loader/loader.go (1)
  • Load (88-111)
internal/config_loader/types.go (4)
  • Metadata (14-18)
  • APICall (107-115)
  • CaptureField (124-127)
  • Resource (170-177)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/k8s_client/discovery.go (1)
  • Discovery (14-29)
internal/config_loader/validator_test.go (3)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/config_loader/validator.go (1)
  • ValidationErrors (30-32)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/criteria/evaluator_test.go (3)
internal/criteria/types.go (2)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/evaluator.go (5)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
  • EvaluationResult (12-23)
  • IsFieldNotFound (584-587)
  • FieldNotFoundError (572-576)
internal/config_loader/constants.go (1)
  • FieldValue (72-72)
internal/hyperfleet_api/types.go (1)
internal/k8s_client/client.go (1)
  • ClientConfig (26-35)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (368-383)
internal/criteria/evaluator.go (2)
  • ConditionDef (291-295)
  • NewEvaluator (48-55)
internal/hyperfleet_api/client_test.go (2)
internal/hyperfleet_api/types.go (3)
  • ClientConfig (38-54)
  • DefaultClientConfig (57-66)
  • Response (159-172)
pkg/errors/api_error.go (2)
  • NewAPIError (123-134)
  • IsAPIError (144-150)
internal/config_loader/types.go (3)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
🪛 ast-grep (0.40.0)
test/integration/k8s_client/helper_envtest_prebuilt.go

[warning] 42-44: MinVersionis missing from this TLS configuration. By default, TLS 1.2 is currently used as the minimum when acting as a client, and TLS 1.0 when acting as a server. General purpose web applications should default to TLS 1.3 with all other protocols disabled. Only where it is known that a web server must support legacy clients with unsupported an insecure browsers (such as Internet Explorer 10), it may be necessary to enable TLS 1.0 to provide support. AddMinVersion: tls.VersionTLS13' to the TLS configuration to bump the minimum version to TLS 1.3.
Context: tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
}
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(missing-ssl-minversion-go)

🔇 Additional comments (40)
scripts/build-integration-image.sh (1)

30-30: Security improvement: Sanitize proxy logging.

Replacing the direct echo of proxy values with a generic message prevents exposure of potentially sensitive proxy credentials (usernames, passwords, full URLs) in build logs. The actual proxy configuration is still passed to the build command via --build-arg, so functionality is preserved.

internal/broker_consumer/README.md (1)

46-48: Good documentation clarity improvement.

The updated example now demonstrates reading BROKER_SUBSCRIPTION_ID once and passing it explicitly to both NewSubscriber and Subscribe. This avoids redundant environment variable reads and makes the flow explicit. The comments clearly explain the intent, and the environment variables section (line 86) correctly lists BROKER_SUBSCRIPTION_ID as required, ensuring consistency across the documentation.

Also applies to: 51-51, 63-63, 86-86

pkg/logger/logger_test.go (1)

242-266: Fixed brace placement in TestLoggerChaining restores proper test structure

The corrected closing brace at the end of TestLoggerChaining now cleanly closes the function after both subtests, resolving the previous structural issue without changing test behavior.

test/integration/k8s_client/helper_selector.go (1)

23-24: Interface satisfaction guard is appropriate

The compile-time assertion var _ TestEnv = (*TestEnvPrebuilt)(nil) is idiomatic Go and correctly ensures TestEnvPrebuilt continues to satisfy TestEnv as the code evolves. No changes needed here.

internal/hyperfleet_api/README.md (1)

1-207: API client README is clear and aligned with the described behavior

The README now clearly explains client/request options, retryable status codes (all 5xx plus 408/429), and the WithBody vs WithJSONBody distinction including the Content-Type default. This is a solid, self-contained reference for adapter authors; no changes needed here.

test/testdata/adapter_config_valid.yaml (1)

16-205: Valid test config nicely exercises the new schema and criteria

This test fixture covers hyperfleetApi settings, env/event parameter extraction, API-backed preconditions (including exists), resource manifests + discovery, and post-processing with CEL expressions. It aligns well with the new criteria operators and loader schema and should provide good regression coverage for future changes.

go.mod (2)

116-117: Fix invalid go.yaml.in module paths; should be gopkg.in/yaml

Lines 116–117 reference a non-existent module path:

go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect

The correct path is gopkg.in/yaml (as used in the direct dependency gopkg.in/yaml.v3 v3.0.1). These entries will cause module resolution to fail. Remove them and run go mod tidy to restore correct indirect dependencies automatically.


3-3: Verify go 1.25.0 directive matches your actual toolchain

The module declares go 1.25.0. Using this version is valid only if your developers and CI are actually running Go ≥1.25; otherwise go commands on earlier toolchains will fail with a version error. If the project is currently built/tested with an earlier Go release, lower this to the version you actually support and test (and bump it later once you adopt a newer toolchain).

Makefile (1)

58-58: LGTM!

The Makefile changes cleanly reorganize the integration test workflow around dedicated image-integration-test and test-integration targets, and add a cache clean step before linting to avoid stale cache issues. All changes align well with the expanded integration test infrastructure.

Also applies to: 92-98, 117-117

test/integration/k8s_client/client_integration_test.go (1)

16-30: LGTM! Clean transition to shared test environment.

The introduction of local gvk constants and migration to GetSharedEnv(t) properly decouples tests from test-only k8s_client exports and aligns with the shared container-backed test environment pattern introduced in this PR.

Also applies to: 34-34

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

94-177: LGTM! Well-structured shared environment setup.

The migration to a shared container lifecycle with proper error handling, bearer token authentication, API server readiness checks, and path traversal protection is well-executed. The path resolution logic correctly validates that resolved paths remain within the base directory.

Also applies to: 179-210, 404-434

internal/criteria/evaluator_test.go (1)

1-1060: LGTM! Comprehensive test coverage for the evaluation engine.

This test file provides extensive coverage of the criteria evaluation engine, including context lifecycle, operators, nested field access, null safety, error handling, and result structures. The table-driven test approach is well-executed and maintainable.

test/integration/k8s_client/main_test.go (1)

1-100: LGTM! Well-structured shared test orchestration.

The TestMain implementation properly manages the shared envtest container lifecycle with appropriate checks for short tests, skip flags, container runtime availability, and cleanup. The GetSharedEnv helper correctly uses t.Helper() and fails tests gracefully when setup errors occur.

internal/config_loader/validator_test.go (1)

1-1129: LGTM! Comprehensive validation test coverage.

The test suite thoroughly exercises the config loader's validation framework, covering operators, templates, CEL expressions, K8s manifests, payloads, and error aggregation. The table-driven approach and clear test organization make this maintainable and easy to extend.

internal/config_loader/validator_schema.go (2)

404-434: LGTM! Path traversal protection properly implemented.

The resolvePath function correctly validates that resolved paths remain within the base directory using filepath.Rel and checking for ".." prefix. This addresses the security concern from the previous review about preventing access to files outside the base directory.


32-52: LGTM! Well-structured validation framework.

The SchemaValidator provides a clean, modular validation pipeline with proper separation between structural validation, file reference validation, and file loading. The validator chain approach makes it easy to understand the validation flow and extend with new validators.

Also applies to: 76-261

internal/config_loader/loader_test.go (1)

1-1363: LGTM! Comprehensive loader test coverage.

This test suite thoroughly exercises the config loader's functionality including parsing, validation, file reference handling, manifest loading, and discovery configuration. The tests properly use temporary directories, verify error conditions, and test edge cases. Particularly good coverage of the file reference loading and validation scenarios.

internal/config_loader/accessors.go (4)

37-82: LGTM - Comprehensive variable collection with proper nil safety.

The method correctly handles nil receiver, collects variables from all sources (built-ins, params, precondition captures, post payloads, resources), and properly prefixes resource aliases with FieldResources.


85-172: LGTM - Clean accessor implementations with consistent nil handling.

All accessor methods (GetParamByName, GetRequiredParams, GetResourceByName, GetPreconditionByName, GetPostActionByName, ParamNames, ResourceNames) consistently check for nil receiver and return appropriate zero values. The use of index-based iteration (for i := range) to return pointers to slice elements is correct.


178-198: LGTM - Straightforward accessor implementations.

ParseTimeout correctly returns 0, nil for empty timeout (allowing caller to use default), and GetBaseURL properly documents that environment variable fallback is handled elsewhere.


268-290: LGTM - Robust map normalization with good yaml.v2/v3 compatibility.

The helper correctly handles both map[string]interface{} (yaml.v3) and map[interface{}]interface{} (yaml.v2) formats, with fallback string conversion for non-string keys.

internal/config_loader/validator.go (6)

19-51: LGTM - Well-designed error aggregation types.

The ValidationError and ValidationErrors types provide clear error context with path information and a clean multi-error format. The HasErrors() method provides a convenient check.


76-97: Validation continues after CEL environment initialization failure.

When initCELEnv() fails, an error is added but validation continues. This means validateCELExpressions() will silently skip CEL validation (line 362-364 checks v.celEnv == nil). This is reasonable behavior, but consider whether failing fast would be preferable for critical initialization errors.

The current approach allows collecting all validation errors in one pass. Verify this is the intended behavior for your use case.


180-181: Template regex may miss edge cases with complex template syntax.

The regex \{\{\s*\.([a-zA-Z_][a-zA-Z0-9_\.]*)\s*(?:\|[^}]*)?\}\} handles basic variables and pipes, but may not correctly parse:

  • Nested template actions like {{ if .foo }}{{ .bar }}{{ end }}
  • Variables with method calls like {{ .foo.Method }}
  • Variables in range/with blocks

If your templates only use simple variable substitution, this is fine. Otherwise, consider using text/template parsing for more robust variable extraction:

tmpl, _ := template.New("").Parse(s)
// Walk the AST to extract variable references

318-358: LGTM - Dynamic CEL environment initialization.

The initialization correctly:

  • Enables optional types for safe chaining
  • Deduplicates root variables from nested paths
  • Uses DynType appropriately for unknown types at validation time
  • Ensures resources map is always available

439-465: Inline manifest with ref key is not validated after loading.

When a manifest has a ref key (single file reference), the code notes "content will have been loaded into Manifest by loadFileReferences" but doesn't re-validate the loaded content. The loaded manifest would be a map[string]interface{} replacing the ref structure.

Verify that after loadFileReferences runs, the manifest is properly re-validated. If the loaded content replaces resource.Manifest, the check at line 445 should pick it up on a second validation pass.


468-499: LGTM - Comprehensive K8s manifest field validation.

The validation checks for required fields (apiVersion, kind, metadata), validates metadata.name presence, and ensures apiVersion and kind are non-empty strings.

internal/config_loader/loader.go (4)

16-40: LGTM - Well-organized constants and valid HTTP methods.

The API version constant, expected kind, and HTTP methods map provide clear configuration validation anchors. Pre-building ValidHTTPMethodsList avoids repeated allocations for error messages.


54-73: LGTM - Clean functional options implementation.

The options follow the standard Go functional options pattern correctly. Each option modifies a single field with clear semantics.


88-111: LGTM - Robust file loading with automatic base directory detection.

The Load function correctly:

  • Falls back to environment variable when path is empty
  • Converts to absolute path for reliable directory extraction
  • Prepends WithBaseDir to allow user override
  • Delegates to Parse for actual processing

140-187: LGTM - Well-structured validation pipeline with clear ordering.

The pipeline follows a logical order:

  1. Core structural validators (always run)
  2. Adapter version validation (optional)
  3. File reference validation and loading (when baseDir set)
  4. Semantic validation (optional, can be skipped)

Error wrapping provides good context for each validation phase.

internal/criteria/cel_evaluator.go (4)

15-50: LGTM - Well-designed result type with clear error semantics.

CELResult cleanly separates success/error states with Error/ErrorReason fields and provides convenient helper methods (HasError, IsSuccess). The documentation clearly explains when each field is populated.


71-85: LGTM - Dynamic CEL environment construction.

The environment is correctly built by enabling optional types and registering variables with inferred types from context data.


131-141: Empty expression evaluates to true - verify this is intentional.

When expression is empty after trimming, the method returns Matched: true. This is a design choice that treats "no condition" as "always passes". Ensure this semantic is documented and expected by callers.

If an empty expression should be an error instead, consider:

if expression == "" {
    return &CELResult{
        Error: fmt.Errorf("empty expression"),
        ErrorReason: "expression cannot be empty",
    }
}

320-365: LGTM - Robust value formatting with proper escaping.

formatCELValue correctly:

  • Uses strconv.Quote for proper string escaping (addressing past review)
  • Handles all primitive types with correct CEL syntax
  • Recursively formats slices/arrays
  • Falls back to reflection for typed slices
internal/hyperfleet_api/types.go (4)

15-31: LGTM - Well-defined backoff strategies and sensible defaults.

The backoff strategies cover common patterns, and default values (10s timeout, 3 retries, exponential backoff, 1s-30s delay range) are reasonable for API client usage.


37-66: LGTM - Comprehensive client configuration with factory function.

ClientConfig covers all necessary HTTP client settings, and DefaultClientConfig() initializes all fields with sensible defaults including an empty (but initialized) DefaultHeaders map.


72-152: LGTM - Clean request options pattern with per-request overrides.

The Request type correctly uses pointers for optional override fields (RetryAttempts, RetryBackoff) to distinguish "not set" from zero values. The functional options are well-designed and WithJSONBody conveniently sets both body and Content-Type.


215-237: LGTM - Clean and complete client interface.

The Client interface provides all essential HTTP methods with consistent signatures, context support, and the BaseURL() accessor for introspection. The use of variadic RequestOption for optional configuration is idiomatic.

internal/criteria/cel_evaluator_test.go (1)

11-571: Comprehensive and targeted CEL evaluator test coverage

This suite exercises the key CEL paths (construction, safe/unsafe evaluation, helper methods, condition→CEL translation, and value formatting) with realistic nested data and edge cases. The scenarios look well‑chosen and should give strong confidence in the CEL integration.

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: 3

♻️ Duplicate comments (1)
internal/config_loader/README.md (1)

74-78: Add language specifier to validation error code fence (markdownlint MD040).

The fenced block showing validation errors lacks a language; please change it to start with ```text to satisfy MD040 and improve readability.

-Validation errors are descriptive:
-```
+Validation errors are descriptive:
+```text
 spec.params[0].name is required
 spec.preconditions[1].apiCall.method must be one of: GET, POST, PUT, PATCH, DELETE

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (13)</summary><blockquote>

<details>
<summary>pkg/logger/logger_test.go (1)</summary><blockquote>

`185-193`: **Stronger assertions on context values look good**

The additional nil check plus explicit equality assertion make this test much more robust and will catch both missing and incorrect values. Using `%v` for the interface-typed key/value is appropriate here.


If you ever need to support non-comparable types as context values, consider switching to `reflect.DeepEqual` for the comparison, but for the current scalar types this is not necessary.

</blockquote></details>
<details>
<summary>Makefile (1)</summary><blockquote>

`58-58`: **CONTAINER_CMD fallback approach is functional but awkward.**

When CONTAINER_CMD expands in a command like `$(CONTAINER_CMD) build ...`, the shell parses it as `sh -c '...' build ...`, which ignores trailing build arguments. However, the guard at line 154-157 (checking `CONTAINER_RUNTIME == none`) prevents this from being executed in the `image` target. The fallback is defensive but consider clarifying a comment that CONTAINER_RUNTIME should always be checked before using CONTAINER_CMD.


Consider adding an inline comment to clarify the relationship:

```diff
 else
     CONTAINER_RUNTIME := none
-    CONTAINER_CMD := sh -c 'echo "No container runtime found. Please install Docker or Podman." && exit 1'
+    # Fallback: should not be invoked due to CONTAINER_RUNTIME guard in image target (line 154)
+    CONTAINER_CMD := sh -c 'echo "No container runtime found. Please install Docker or Podman." && exit 1'
 endif
internal/broker_consumer/consumer.go (1)

133-136: Good nil guard; consider Go style for return type.

The nil guard is a solid defensive addition that prevents panics. However, the parentheses around the single return value are non-idiomatic Go.

-func Close(subscriber Subscriber) (error) {
+func Close(subscriber Subscriber) error {
test/integration/k8s_client/client_integration_test.go (1)

577-585: Clarify test expectations for update without resourceVersion.

The test comment states "behavior may vary" when updating without a resourceVersion, which makes the test assertion unclear. The conditional logic verifies the update only if it succeeds, but doesn't assert any expected behavior.

Consider either:

  1. Asserting that the update should fail (if that's the expected behavior for controller-runtime), or
  2. Documenting why the behavior is legitimately non-deterministic and what guarantees the test is verifying.
test/integration/k8s_client/helper_selector.go (1)

54-67: Consider simplifying the contains function.

The current implementation has nested conditions that could be simplified. Since this is checking for substring presence in error messages, consider using the standard library:

+import "strings"
+
-// contains checks if a string contains a substring
-func contains(s, substr string) bool {
-	return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
-		(len(s) > len(substr) && containsSubstring(s, substr)))
-}
-
-func containsSubstring(s, substr string) bool {
-	for i := 0; i <= len(s)-len(substr); i++ {
-		if s[i:i+len(substr)] == substr {
-			return true
-		}
-	}
-	return false
+func contains(s, substr string) bool {
+	return strings.Contains(s, substr)
 }
test/integration/k8s_client/main_test.go (1)

84-86: Check type assertion result to avoid potential panic.

The type assertion to *TestEnvPrebuilt doesn't check the ok boolean. While this is currently safe given the codebase structure, defensive programming suggests checking the assertion result.

 	if sharedEnv != nil {
 		println()
 		println("🧹 Cleaning up shared envtest container...")
 		if env, ok := sharedEnv.(*TestEnvPrebuilt); ok {
 			env.CleanupSharedEnv()
+		} else {
+			println("⚠️  Warning: Shared environment is not TestEnvPrebuilt, cleanup skipped")
 		}
 	}
test/integration/k8s_client/helper_envtest_prebuilt.go (1)

42-46: Add TLS MinVersion even for test environments.

While InsecureSkipVerify is necessary for envtest's self-signed certificates, consider adding MinVersion: tls.VersionTLS12 (or TLS 1.3) to follow security best practices even in test code. This documents the expected TLS version and prevents accidental downgrades.

Based on learnings from static analysis tools.

 		Transport: &http.Transport{
 			TLSClientConfig: &tls.Config{
 				InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
+				MinVersion:         tls.VersionTLS12,
 			},
 		},
test/integration/config-loader/loader_template_test.go (1)

16-28: Consider making getProjectRoot more robust than a raw string index.

Using strings.Index(filename, projectName) works but can break if the path contains the project name multiple times or is laid out differently. As a follow-up, you could walk up from filepath.Dir(filename) until you find a go.mod or a directory named projectName, which tends to be more stable across environments. The current implementation is fine for now, though.

internal/config_loader/accessors.go (1)

21-24: Consider returning a copy to prevent mutation.

BuiltinVariables() returns the internal slice directly. If any caller modifies the returned slice, it would affect all subsequent calls. Consider returning a copy for safety.

 // BuiltinVariables returns the list of built-in variables always available in templates/CEL
 func BuiltinVariables() []string {
-	return builtinVariables
+	result := make([]string, len(builtinVariables))
+	copy(result, builtinVariables)
+	return result
 }
internal/config_loader/validator_schema.go (1)

268-434: Path traversal protection in resolvePath looks solid; consider caching base path once.

The new resolvePath correctly normalizes baseDir, rejects paths that escape it via filepath.Rel, and surfaces clear errors, which closes the earlier traversal hole. The only nit is that filepath.Abs/filepath.Clean for v.baseDir are recomputed on every call; if you expect many manifest/buildRef files per config, you could cache a pre‑cleaned base path on SchemaValidator (e.g., in a separate field initialized in NewSchemaValidator) to avoid repeated work. Behavior would stay identical.

internal/criteria/cel_evaluator.go (1)

229-241: Clarify or tighten EvaluateBool semantics for non‑boolean results.

EvaluateBool returns result.Matched when the CEL value is not a bool, which means expressions like "foo" or a non‑empty list will be treated as true rather than causing an error, despite the doc saying the expression “should return a boolean.” To avoid surprising behavior, consider either:

  • returning an explicit error when result.Value is not bool, or
  • documenting that EvaluateBool degrades to “non‑empty means true” semantics for non‑boolean results.
internal/hyperfleet_api/client.go (1)

165-246: Document that RetryAttempts is clamped to a minimum of 1.

Do normalizes retryAttempts so any value < 1 results in exactly one attempt. That’s a sensible guard, but it means “0” or negative values do not disable the call or retries; they still perform one HTTP attempt. Consider updating the ClientConfig.RetryAttempts and WithRequestRetryAttempts docs/comments to state that values < 1 are treated as 1 so callers don’t assume “0” has a special meaning.

test/integration/testutil/container.go (1)

237-305: Consider bounding docker/podman cleanup commands with a timeout.

forceCleanupContainer and CleanupLeakedContainers call exec.Command(...).Run/CombinedOutput() directly. If docker/podman were to hang (e.g., daemon issues), tests could block indefinitely in a cleanup path. As a small resilience improvement, consider using exec.CommandContext with a short timeout (e.g., 10–30s) for these best‑effort cleanup calls so they fail fast instead of potentially stalling the test run.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95bdc0e and 6f0fed4.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (54)
  • Makefile (3 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (5 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (6 hunks)
  • internal/broker_consumer/README.md (1 hunks)
  • internal/broker_consumer/consumer.go (3 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/constants.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (6 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • internal/k8s_client/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • pkg/logger/logger_test.go (2 hunks)
  • scripts/build-integration-image.sh (1 hunks)
  • scripts/run-integration-tests.sh (1 hunks)
  • scripts/start-envtest.sh (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/broker_consumer/setup_test.go (1 hunks)
  • test/integration/broker_consumer/testutil_publisher.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (32 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_selector.go (2 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (0 hunks)
  • test/integration/k8s_client/main_test.go (1 hunks)
  • test/integration/testutil/container.go (7 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • test/integration/k8s_client/helper_testcontainers_k3s.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • test/integration/broker_consumer/setup_test.go
  • internal/k8s_client/discovery.go
  • test/integration/broker_consumer/adapter_integration_test.go
  • configs/broker-configmap-pubsub-template.yaml
  • internal/criteria/types.go
  • internal/k8s_client/client_test.go
  • configs/templates/cluster-status-payload.yaml
  • internal/criteria/README.md
  • scripts/run-integration-tests.sh
  • internal/hyperfleet_api/README.md
  • configs/templates/job.yaml
  • test/integration/config-loader/config_criteria_integration_test.go
  • internal/config_loader/constants.go
  • internal/hyperfleet_api/client_test.go
  • scripts/build-integration-image.sh
  • configs/templates/deployment.yaml
🧰 Additional context used
🧬 Code graph analysis (13)
internal/criteria/cel_evaluator.go (2)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/criteria/evaluator.go (1)
  • ConditionDef (291-295)
cmd/adapter/main.go (4)
internal/config_loader/loader.go (3)
  • EnvConfigPath (22-22)
  • Load (88-111)
  • WithAdapterVersion (55-59)
internal/config_loader/types.go (2)
  • Metadata (14-18)
  • HyperfleetAPIConfig (37-42)
internal/hyperfleet_api/types.go (5)
  • Client (216-237)
  • BackoffStrategy (13-13)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
internal/hyperfleet_api/client.go (6)
  • ClientOption (37-37)
  • WithBaseURL (101-105)
  • WithTimeout (66-70)
  • WithRetryAttempts (73-77)
  • WithRetryBackoff (80-84)
  • NewClient (117-145)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (6-11)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (461-473)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/config_loader/validator.go (4)
internal/config_loader/types.go (3)
  • AdapterConfig (6-11)
  • Condition (130-134)
  • APICall (107-115)
internal/config_loader/constants.go (27)
  • FieldSpec (9-9)
  • FieldPreconditions (19-19)
  • FieldConditions (50-50)
  • FieldAPICall (48-48)
  • FieldURL (57-57)
  • FieldBody (60-60)
  • FieldHeaders (59-59)
  • FieldHeaderValue (65-65)
  • FieldResources (20-20)
  • FieldManifest (78-78)
  • FieldDiscovery (81-81)
  • FieldNamespace (92-92)
  • FieldByName (93-93)
  • FieldBySelectors (94-94)
  • FieldLabelSelector (99-99)
  • FieldPost (21-21)
  • FieldPostActions (104-104)
  • FieldPayloads (41-41)
  • FieldBuild (42-42)
  • FieldExpression (51-51)
  • FieldRef (86-86)
  • FieldRefs (87-87)
  • FieldManifestItems (79-79)
  • FieldAPIVersion (109-109)
  • FieldKind (110-110)
  • FieldMetadata (10-10)
  • FieldName (31-31)
internal/criteria/types.go (3)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
internal/config_loader/loader.go (1)
  • Parse (114-130)
test/integration/k8s_client/client_integration_test.go (1)
test/integration/k8s_client/main_test.go (1)
  • GetSharedEnv (94-99)
test/integration/k8s_client/main_test.go (2)
test/integration/k8s_client/helper_selector.go (1)
  • TestEnv (15-21)
test/integration/k8s_client/helper_envtest_prebuilt.go (1)
  • TestEnvPrebuilt (80-86)
internal/config_loader/accessors.go (2)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Parameter (51-58)
  • Resource (170-177)
  • Precondition (98-104)
  • PostAction (198-201)
  • HyperfleetAPIConfig (37-42)
internal/config_loader/constants.go (1)
  • FieldResources (20-20)
internal/config_loader/validator_test.go (4)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/config_loader/types.go (2)
  • AdapterConfig (6-11)
  • Payload (67-78)
internal/config_loader/validator.go (2)
  • Validate (508-510)
  • ValidationErrors (30-32)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/k8s_client/client.go (2)
internal/hyperfleet_api/types.go (1)
  • Client (216-237)
pkg/errors/error.go (1)
  • KubernetesError (203-205)
internal/hyperfleet_api/client.go (1)
pkg/errors/api_error.go (1)
  • NewAPIError (123-134)
internal/config_loader/types.go (3)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/config_loader/validator_schema.go (5)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Metadata (14-18)
  • APICall (107-115)
  • Resource (170-177)
  • SelectorConfig (187-189)
  • Precondition (98-104)
internal/config_loader/loader.go (4)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
  • ValidHTTPMethodsList (39-39)
internal/config_loader/constants.go (25)
  • FieldSpec (9-9)
  • FieldAdapter (15-15)
  • FieldVersion (26-26)
  • FieldParams (18-18)
  • FieldName (31-31)
  • FieldSource (32-32)
  • FieldPreconditions (19-19)
  • FieldAPICall (48-48)
  • FieldExpression (51-51)
  • FieldConditions (50-50)
  • FieldResources (20-20)
  • FieldManifest (78-78)
  • FieldDiscovery (81-81)
  • FieldNamespace (92-92)
  • FieldByName (93-93)
  • FieldBySelectors (94-94)
  • FieldLabelSelector (99-99)
  • FieldPost (21-21)
  • FieldPostActions (104-104)
  • FieldPayloads (41-41)
  • FieldMethod (56-56)
  • FieldURL (57-57)
  • FieldBuildRef (43-43)
  • FieldRef (86-86)
  • FieldRefs (87-87)
internal/k8s_client/discovery.go (1)
  • Discovery (14-29)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (4)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (368-383)
🪛 ast-grep (0.40.0)
test/integration/k8s_client/helper_envtest_prebuilt.go

[warning] 42-44: MinVersionis missing from this TLS configuration. By default, TLS 1.2 is currently used as the minimum when acting as a client, and TLS 1.0 when acting as a server. General purpose web applications should default to TLS 1.3 with all other protocols disabled. Only where it is known that a web server must support legacy clients with unsupported an insecure browsers (such as Internet Explorer 10), it may be necessary to enable TLS 1.0 to provide support. AddMinVersion: tls.VersionTLS13' to the TLS configuration to bump the minimum version to TLS 1.3.
Context: tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
}
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(missing-ssl-minversion-go)

🪛 markdownlint-cli2 (0.18.1)
internal/config_loader/README.md

75-75: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (69)
pkg/logger/logger_test.go (1)

257-266: Fixed block structure for TestLoggerChaining

The function body and subtests now close cleanly; this removes the risk of confusing brace structure without changing behavior.

scripts/start-envtest.sh (1)

28-28: Appropriate configuration for test Kubernetes API.

The --anonymous-auth=true flag is appropriate for this envtest environment. Combined with --authorization-mode=AlwaysAllow (line 27), it creates a fully permissive API that allows tests to interact with the Kubernetes client without managing authentication infrastructure. This is standard for local test setups and the PR validation confirms all integration tests pass with this configuration.

Makefile (2)

92-98: New integration test targets are well-structured.

Both image-integration-test and test-integration targets are properly marked .PHONY, clearly documented with emojis and descriptions, and delegate to bash scripts. The parameterization of TEST_TIMEOUT in test-integration is good for consistency with unit test timeout.

Verify that the referenced bash scripts (scripts/build-integration-image.sh and scripts/run-integration-tests.sh) exist, are executable, and work correctly with the container runtime detection logic.


117-117: Cache clean before lint is a best practice.

Adding golangci-lint cache clean before the lint run ensures a fresh cache and avoids stale state issues that could mask newly introduced linting violations.

internal/broker_consumer/consumer.go (1)

74-86: LGTM!

The logging simplification is clean and removes redundant broker type information that is likely already known from context.

internal/broker_consumer/README.md (1)

42-57: LGTM!

Documentation accurately reflects the updated NewSubscriber signature and correctly demonstrates using the resolved subscriptionID return value for the subsequent Subscribe call.

test/integration/broker_consumer/testutil_publisher.go (1)

41-43: LGTM! Improved handling of topic-exists race condition.

The synchronous retry after detecting "topic already exists" correctly handles the race between publisher and subscriber topic creation. The assumption that this error occurs before message delivery (allowing safe retry without duplicates) aligns with standard Pub/Sub semantics.

internal/k8s_client/types.go (1)

48-50: LGTM: Defensive nil check.

The nil check prevents potential panics and returns a safe zero value, which is appropriate for this utility function.

internal/k8s_client/README.md (3)

38-38: LGTM: Architecture diagram updated.

The addition of DiscoverResources() to the architecture diagram appropriately reflects the new discovery interface.


214-230: LGTM: Clear alternative documented.

The "List Resources (Low-Level)" section appropriately positions ListResources as a simpler alternative to the Discovery interface, with clear usage examples.


61-73: Verify DiscoveryConfig and DiscoverResources implementation.

The documentation describes DiscoveryConfig with Namespace, LabelSelector, and ByName fields, and a DiscoverResources() method. Confirm these match the actual implementation in discovery.go.

Also applies to: 184-212

internal/k8s_client/client.go (4)

162-169: LGTM: Helpful guidance added.

The added documentation clearly describes parameters and appropriately directs users to DiscoverResources() for more flexible discovery patterns.


181-189: LGTM: Robust label selector parsing.

Using metav1.ParseToLabelSelector and metav1.LabelSelectorAsSelector is the correct approach for parsing label selectors. This is more robust than direct string parsing and properly handles selector syntax.


230-232: LGTM: Consistent error handling.

Returning the raw conflict error (without wrapping) is consistent with how NotFound errors are handled elsewhere in this file, allowing callers to use apierrors.IsConflict() checks.


264-303: Documentation correction: JSON Merge Patch confirmed.

The updated documentation correctly identifies the patch type as JSON Merge Patch (RFC 7386). The code uses types.MergePatchType, which is indeed JSON Merge Patch, not Strategic Merge Patch.

test/integration/k8s_client/client_integration_test.go (1)

16-30: LGTM: Local GVK definitions avoid test-only exports.

The local gvk struct with pre-defined GroupVersionKinds is a clean approach to avoid depending on test-only exports from the main package. The definitions for standard Kubernetes resources (Namespace, Pod, Service, ConfigMap, Secret) are correct.

test/integration/k8s_client/helper_envtest_prebuilt.go (2)

36-77: LGTM: API server readiness polling with appropriate backoff.

The waitForAPIServerReady function implements a robust readiness check with:

  • Proper timeout handling
  • Bearer token authentication
  • Short backoff retries (500ms)
  • Clear error messages distinguishing network errors from status code failures

This ensures the shared environment is fully ready before tests begin.


94-177: LGTM: Well-structured shared environment setup.

The setupSharedTestEnv function properly:

  • Uses testutil.StartSharedContainer for lifecycle management
  • Waits for API server readiness before returning
  • Creates the Kubernetes client with appropriate TLS settings
  • Handles errors gracefully with cleanup on failure
  • Creates the default namespace

The shift from per-test setup to a shared environment will significantly improve test performance.

go.mod (1)

9-135: Dependency updates for CEL, YAML, and K8s look consistent with new features.

The added/updated dependencies (e.g., cel-go, cel.dev/expr, antlr4-go, yaml.v3, k8s.io/api as indirect) align with the CEL-based criteria evaluation and config loader work; I don’t see versioning or obvious duplication issues in this diff. Just ensure your normal go mod tidy + CI pipeline has been run after these changes so the module graph and go.sum stay in sync.

test/testdata/templates/deployment.yaml (1)

1-27: Deployment template is well-structured for test usage.

Template variables (clusterId, metadata.name) and labels look consistent with the config loader/validator tests, and the manifest is a valid minimal Deployment for integration tests.

configs/README.md (1)

22-71: Broker configuration docs now align with the updated env vars.

Switching to BROKER_SUBSCRIPTION_ID and keeping BROKER_GOOGLEPUBSUB_PROJECT_ID / optional topic settings matches the current broker_consumer behavior and recent tests; the quick-start and env table look consistent.

test/integration/config-loader/loader_template_test.go (1)

30-150: Template and test-config integration coverage looks solid.

These tests exercise real-world configs (configs/adapter-config-template.yaml and adapter_config_valid.yaml) and validate key pieces (params, preconditions, resources, post config, captures). This is good end-to-end coverage for the loader and accessors.

cmd/adapter/main.go (3)

34-45: Flag wiring for --config into run is straightforward and clear.

Using a global configPath flag and passing it into config_loader.Load inside run() keeps startup configuration simple; behavior with the fallback ADAPTER_CONFIG_PATH env var is well-documented in the flag help text and comments.


62-83: Config load + API client initialization flow is well-structured.

Loading the adapter config once at startup (with WithAdapterVersion(version)) and immediately constructing the HyperFleet API client from Spec.HyperfleetAPI—with clear error logging and early exit on failure—gives a clean failure mode and ensures misconfigurations are caught before subscribing to the broker.


182-222: createAPIClient correctly maps config fields and validates backoff strategy.

Timeout parsing, retry attempts, and backoff strategy validation are all handled cleanly, and you correctly rely on NewClient to enforce a non-empty base URL (config wins, then env fallback). This is a good separation of concerns between config interpretation and client construction.

internal/criteria/evaluator_test.go (1)

11-1059: Evaluator tests provide comprehensive coverage of operators, paths, and null-safety.

These tests thoroughly exercise the evaluation context, all operators, nested field access (including error paths and FieldNotFoundError), numeric coercion, condition/result helpers, and null-safe access patterns. This gives strong confidence in the criteria implementation.

internal/config_loader/validator_test.go (1)

12-1128: Validator tests give strong coverage for operators, templates, manifests, and payloads.

Condition operator validation, template variable checking (including captured fields and builtins), CEL expression validation, K8s manifest/manifestItems rules, payload mutual-exclusion semantics, and error aggregation are all well-covered here. This is a solid test suite for the config validation pipeline; any additional edge cases (e.g., more notIn variants) can be added incrementally as needed.

internal/criteria/evaluator_scenarios_test.go (7)

1-9: LGTM - Well-structured test file.

Good use of stretchr/testify for assertions and proper package organization.


10-89: LGTM - Comprehensive real-world scenario test.

Good coverage of precondition evaluation patterns with realistic cluster data structures.


91-154: LGTM - Good use of isolated context.

The use of local context in the "replicas equal ready replicas" subtest (lines 132-153) is a good practice for avoiding shared state issues in parallel tests.


156-190: LGTM!

Properly validates nested field access and the contains operator on slices.


192-251: LGTM - Good coverage of map key containment.

Testing both map[string]interface{} and map[string]string is important for Kubernetes label/annotation patterns.


253-284: LGTM - Clear test intent.

The test comments at lines 263 and 274-275 now clearly explain the expected behavior for terminating cluster scenarios.


286-365: LGTM - Idiomatic table-driven test.

Good boundary condition coverage and proper isolation with per-subtest context creation.

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

26-26: LGTM - Operator list updated.

Good addition of "exists" to the supported operators documentation.


123-128: LGTM - Well-defined optional parameter.

The imageTag parameter with a sensible default and environment variable source is a clean addition.


145-158: LGTM - Cleaner capture syntax.

The new capture block with name and field is more intuitive than the previous storeResponseAs/extract approach. This addresses the previous review feedback about cognitive load.


179-183: LGTM!

Consistent use of the new capture syntax for the validation availability precondition.


304-304: LGTM - Better semantic naming.

Renaming params to payloads better reflects the purpose of this section.

internal/config_loader/accessors.go (7)

1-9: LGTM - Clean package structure.

Good imports and appropriate constant definition for the environment variable.


37-82: LGTM - Comprehensive variable aggregation.

Good nil-safety and thorough collection from all variable sources (builtins, params, captures, payloads, and resource aliases).


84-148: LGTM - Consistent accessor pattern.

All lookup accessors properly use range i to return pointers to actual slice elements rather than copies, and have consistent nil-safety checks.


150-172: LGTM!

Efficient pre-allocation and proper nil-safety.


178-198: LGTM - Good documentation.

The comment on GetBaseURL() clearly explains that environment variable fallback is handled by the client, preventing confusion about where defaults come from.


204-262: LGTM - Flexible manifest handling.

Good support for both single ref and refs array patterns, with proper nil-safety throughout.


268-290: LGTM - Good YAML compatibility layer.

This handles the known difference between yaml.v2 (map[interface{}]interface{}) and yaml.v3 (map[string]interface{}), ensuring robustness across different YAML libraries.

pkg/errors/api_error.go (6)

1-8: LGTM - Clean imports.

All imports are necessary and appropriately used.


14-45: LGTM - Comprehensive error context.

Good separation between errors that occur before receiving a response (StatusCode == 0) and HTTP-level errors in the Error() method.


47-50: LGTM!

Proper implementation for error unwrapping support.


56-99: LGTM - Good status code coverage.

The IsTimeout method correctly handles both HTTP 408 and context.DeadlineExceeded, covering both server-reported and client-side timeouts.


101-134: LGTM!

Clean response body helpers and explicit constructor.


136-150: LGTM - Idiomatic Go error type checking.

Good use of errors.As for proper support of wrapped errors, with clear example usage in the docstring.

internal/config_loader/validator.go (10)

1-13: LGTM - Appropriate imports.

Good use of CEL library and internal criteria package for operator validation.


19-51: LGTM - Clean error aggregation.

Good formatting in ValidationErrors.Error() with count and indented list for readability.


57-97: LGTM - Good validation orchestration.

Continuing validation after CEL environment initialization failure allows other validations to run and report multiple errors at once.


112-174: LGTM - Thorough operator validation.

Good coverage of operator-specific value requirements, including the exists operator not requiring a value and in/notIn requiring arrays.


180-181: LGTM - Good template regex.

The regex correctly captures template variables including those with pipe filters.


260-290: LGTM - Smart variable resolution.

Good handling of both simple root variables and resource aliases like resources.clusterNamespace.metadata.namespace.


318-358: LGTM - Dynamic CEL environment.

Good approach using cel.DynType since actual variable types aren't known at config validation time. The pre-allocation and deduplication of root variables is efficient.


387-409: LGTM - Pragmatic CEL validation.

Logging type-check failures at debug level rather than failing validation is appropriate since CEL expressions work with dynamic data at runtime.


439-499: LGTM - Appropriate K8s manifest validation.

Validating required fields (apiVersion, kind, metadata, metadata.name) is appropriate for config-time validation. Full K8s schema validation would happen at apply time.


505-509: LGTM - Clean backward-compatible API.

The standalone Validate function provides a simple entry point while NewValidator allows for more control if needed.

internal/config_loader/loader_test.go (1)

13-362: Loader and validation tests provide strong end‑to‑end coverage.

The scenarios here exercise the full loader pipeline (structural validation, params/preconditions/resources, discovery, file refs, manifest.refs, adapter version, API version, and condition value/values aliasing) with resilient Contains assertions on errors. This should catch most regressions in the config surface without being overly brittle.

internal/config_loader/loader.go (1)

84-187: Loader pipeline and option ordering look well‑structured.

Load deriving baseDir and prepending WithBaseDir before user options, plus runValidationPipeline’s staged flow (schema/core checks → adapter version → file refs validate/load → semantic Validate), gives a clear separation of concerns and predictable error wrapping. The behavior also matches how the tests exercise WithSkipSemanticValidation and file‑reference handling.

internal/hyperfleet_api/types.go (1)

24-237: HTTP client type surface is clean and extensible.

The separation between ClientConfig defaults, per‑request overrides on Request, and the Response helpers (IsSuccess/IsRetryable etc.) reads well and should be easy to evolve. Leaving BaseURL unset in DefaultClientConfig and deferring enforcement to the constructor is also a good choice.

test/testdata/adapter_config_valid.yaml (1)

16-196: Valid adapter config fixture exercises the full schema surface.

This sample config touches env and event params, preconditions (API call + captures + conditions + CEL expression), resource manifests with both inline and manifest.ref, discovery by name/selectors, and post payload/postAction wiring. It aligns with the new schema validator requirements and should serve well as a canonical “valid” fixture for integration tests.

internal/criteria/cel_evaluator_test.go (1)

11-571: Thorough CEL evaluator test coverage looks solid.

The tests exercise evaluator construction, nested data handling, safe vs unsafe evaluation paths, CEL expression generation, and value formatting (including error paths) in a way that matches the current CELEvaluator and helper APIs; I don’t see changes needed here.

internal/config_loader/types.go (1)

49-168: Payload and Condition config modeling/validation look appropriate.

Payload.Validate cleanly enforces the mutual exclusivity and presence of build vs buildRef, and the custom Condition.UnmarshalYAML correctly supports value/values aliasing while rejecting ambiguous configs with both set; this aligns well with the criteria/CEL integration expectations.

internal/criteria/evaluator.go (1)

37-287: Evaluator structure and CEL integration look coherent.

Lazy CEL environment initialization via getCELEvaluator, the separation between classic operator-based evaluation and CEL-based helpers, and the ConditionsResult/EvaluationResult shapes all look consistent and maintainable; no issues in this part.

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

♻️ Duplicate comments (1)
internal/config_loader/README.md (1)

74-78: Add language to validation error fence to satisfy markdownlint

The validation error example fence still lacks a language; adding text keeps formatting and fixes MD040.

-Validation errors are descriptive:
-```
+Validation errors are descriptive:
+```text
 spec.params[0].name is required
 spec.preconditions[1].apiCall.method must be one of: GET, POST, PUT, PATCH, DELETE

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (18)</summary><blockquote>

<details>
<summary>Makefile (1)</summary><blockquote>

`117-117`: **Lint target now clears cache before running.**

The addition of `golangci-lint cache clean` before `golangci-lint run` ensures consistent linting results across runs. This is a reasonable improvement, though it does add a small overhead to every lint invocation. Consider whether this should be conditional or moved to a separate target if cache clearing becomes necessary only in specific scenarios.

</blockquote></details>
<details>
<summary>internal/broker_consumer/consumer.go (1)</summary><blockquote>

`133-133`: **Consider removing redundant parentheses around return type.**

The parentheses around `error` are valid but unnecessary. Go style typically writes `error` directly without parentheses.


Apply this diff:

```diff
-func Close(subscriber Subscriber) (error) {
+func Close(subscriber Subscriber) error {
internal/k8s_client/types.go (1)

48-50: Good defensive nil-check!

The nil-check prevents a potential panic and safely returns an empty GroupVersionKind. This is a solid improvement for robustness.

Consider updating the function documentation to mention the nil-handling behavior, so callers are aware that passing nil returns an empty GVK.

Example documentation addition:

 // GVKFromUnstructured extracts GroupVersionKind from an unstructured object.
 //
+// Returns an empty GroupVersionKind if obj is nil.
+//
 // This is useful when you've rendered a template and need to extract the GVK
 // before creating the resource.
configs/broker-configmap-pubsub-template.yaml (1)

1-118: Document the breaking change and migration path.

The ConfigMap key rename is a breaking change for existing deployments. Consider adding a migration guide or version notes to help operators update their configurations. Optionally, consider backward compatibility (e.g., adapter checks for both old and new keys with a deprecation warning) if feasible.

pkg/logger/logger_test.go (1)

189-193: LGTM: nil check and value comparison are now separate.

The explicit nil check before value comparison improves test clarity. All current test cases have non-nil expected values, so the logic is sound.

Consider using else if for the second check to avoid redundant error messages if val is nil:

 if val == nil {
     t.Errorf("Expected context value for key %v, got nil", tt.expectedKey)
-}
-if val != tt.expectedValue {
+} else if val != tt.expectedValue {
     t.Errorf("Expected context value %v, got %v", tt.expectedValue, val)
 }
test/integration/k8s_client/helper_selector.go (1)

23-57: Custom contains works but could be simplified

The manual contains/containsSubstring implementation is correct, but it reimplements standard library behavior. Unless you’re avoiding the strings import on purpose, using strings.Contains would be simpler and easier to read.

internal/k8s_client/discovery.go (1)

12-122: Discovery API and helper behavior look solid; minor GVK nit

The Discovery interface, DiscoveryConfig methods, and DiscoverResources flow (single-resource vs selector-based) are coherent and nil-safe, and BuildLabelSelector’s sorted output is exactly what you want for deterministic selectors. One optional improvement: when you set the GroupVersionKind on the UnstructuredList, you may want to use a Kind of <Kind>List (e.g., DeploymentList) to better match Kubernetes conventions, though in practice most callers won’t depend on the list’s Kind.

internal/k8s_client/README.md (1)

38-83: DiscoverResources documentation matches the API; minor package-path nit

The new sections showing DiscoveryConfig usage for label- and name-based discovery align well with the DiscoverResources implementation and should make the API easy to adopt. One small doc nit: elsewhere in this README you refer to the high-level package as internal/config-loader, but the actual Go import path uses an underscore (internal/config_loader), so consider adjusting that reference to avoid confusion for readers copy-pasting paths.

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

123-129: Template changes are consistent; consider clarifying the unused payload ref

The new imageTag param and the switch to capture fields (clusterName, region, cloudProvider, vpcId, etc.) are wired consistently through the resources and preconditions, and the post.payloadspostActions flow for clusterStatusPayload matches the new post-processing model. Right now clusterStatusPayloadRef is defined but not consumed by any postAction; if it’s intended purely as an example of buildRef, consider adding a short comment to say so, or alternatively wire a postAction to use {{ .clusterStatusPayloadRef }}.

Also applies to: 145-184, 300-389

test/integration/testutil/container.go (1)

39-41: Container lifecycle improvements look robust; be cautious with leaked-container cleanup scope

Aligning the CleanupTimeout default with the enforced 60s minimum and explicitly terminating partially-started containers before retries significantly reduces the chance of leaked or stuck containers, and the forceCleanupContainer/forceCleanupContainerNoTest fallbacks are a reasonable best-effort when Terminate fails. The new CleanupLeakedContainers and SharedContainer helpers round out the test infra nicely; just make sure callers only pass test-specific images to CleanupLeakedContainers, since it will remove all containers using that image across docker/podman, not just ones started by this test package.

Also applies to: 75-82, 102-118, 157-205, 237-305, 340-515

test/integration/config-loader/loader_template_test.go (1)

18-28: Consider a more robust approach to finding the project root.

The current string-based path search for projectName in the file path can fail if the project is renamed, uses symlinks, or has an unexpected directory structure. The panic on line 25 also produces a less helpful error message during test failures.

Consider these alternatives:

  • Use testing.T and t.TempDir() with relative paths from the test file
  • Search upward for go.mod file to find the project root
  • Use filepath.Abs with relative paths like ../../.. from the test file location

Example with go.mod search:

-func getProjectRoot() string {
-	_, filename, _, ok := runtime.Caller(0)
-	if !ok {
-		panic("runtime.Caller failed")
-	}
-	idx := strings.Index(filename, projectName)
-	if idx == -1 {
-		panic("could not find project root: " + projectName + " not found in path")
-	}
-	return filename[:idx+len(projectName)]
+func getProjectRoot() string {
+	_, filename, _, _ := runtime.Caller(0)
+	dir := filepath.Dir(filename)
+	for {
+		if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
+			return dir
+		}
+		parent := filepath.Dir(dir)
+		if parent == dir {
+			panic("could not find project root (go.mod not found)")
+		}
+		dir = parent
+	}
 }
internal/config_loader/validator.go (1)

180-181: Broaden templateVarRegex to cover {{- .var }} / {{ .var -}} trim forms.

Right now the regex only matches {{ .var }}–style variables; templates that use Go’s common trim markers ({{- .var }}, {{ .var -}}, {{- .var -}}) will be skipped and undefined vars there won’t be validated. Consider allowing optional - alongside whitespace, e.g.:

var templateVarRegex = regexp.MustCompile(`\{\{[-\s]*\.([a-zA-Z_][a-zA-Z0-9_\.]*)[^}]*\}\}`)

That would keep the same capture semantics while covering the trimmed variants as well.

Also applies to: 243-258

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

36-47: Optional: set MinVersion on TLS config to satisfy scanners.

For envtest you already intentionally use InsecureSkipVerify against a self‑signed API server, which is acceptable in this test helper. To quiet static‑analysis findings and make intent clearer, you could also set an explicit minimum TLS version, e.g.:

TLSClientConfig: &tls.Config{
	InsecureSkipVerify: true,          // self-signed envtest certs
	MinVersion:         tls.VersionTLS13,
}

Not required for correctness, but it aligns with the static analysis recommendation.

internal/criteria/cel_evaluator.go (1)

229-241: Consider making EvaluateBool stricter for non‑boolean expressions.

Right now, if the expression doesn’t return a bool, EvaluateBool falls back to result.Matched (i.e. “non‑empty” semantics). That can mask type issues if someone accidentally passes a non‑boolean CEL expression. You may want to instead treat non‑bool results as an error or always return false to keep the API strictly boolean.

internal/config_loader/loader.go (1)

88-111: Validation pipeline wiring is clean; just be aware of file‑ref behavior when using Parse directly.

Load correctly derives baseDir and runs file‑reference validation/loading, followed by semantic validation. When calling Parse directly without a WithBaseDir option, any buildRef / manifest.ref paths won’t be validated or resolved, which is fine if you only support file refs for on‑disk configs but might surprise future callers.

If you ever need to support file refs with raw YAML bytes, consider:

  • Documenting that Parse requires an explicit WithBaseDir for file refs, or
  • Having runValidationPipeline fail when file refs are present but baseDir is empty.

For the current adapter entrypoints, this setup is consistent and correct.

Also applies to: 139-177

internal/hyperfleet_api/client.go (2)

137-142: Consider documenting that custom http.Client timeout takes precedence.

When WithHTTPClient is used, the custom client's timeout (if any) overrides c.config.Timeout. This is reasonable but worth documenting in the WithHTTPClient godoc to avoid confusion.

-// WithHTTPClient sets a custom http.Client
+// WithHTTPClient sets a custom http.Client.
+// Note: The custom client's timeout takes precedence over WithTimeout configuration.
 func WithHTTPClient(client *http.Client) ClientOption {

313-317: Consider limiting response body size to prevent memory exhaustion.

io.ReadAll reads the entire response without bounds. A malicious or misconfigured server could return an extremely large body, causing memory exhaustion.

Consider using io.LimitReader to cap the response size:

 	// Read response body
-	respBody, err := io.ReadAll(httpResp.Body)
+	// Limit response body to prevent memory exhaustion (e.g., 10MB)
+	const maxBodySize = 10 * 1024 * 1024
+	respBody, err := io.ReadAll(io.LimitReader(httpResp.Body, maxBodySize))
internal/config_loader/types.go (1)

179-189: DiscoveryConfig serves YAML representation purpose.

This DiscoveryConfig differs from internal/k8s_client.DiscoveryConfig intentionally: this version uses nested SelectorConfig for YAML ergonomics, while the k8s_client version uses a flat LabelSelector string for runtime operations. Consider adding a brief comment clarifying this distinction to help future maintainers.

-// DiscoveryConfig represents resource discovery configuration
+// DiscoveryConfig represents resource discovery configuration for YAML deserialization.
+// Note: This differs from k8s_client.DiscoveryConfig which uses flat selector strings.
 type DiscoveryConfig struct {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f0fed4 and a535220.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (54)
  • Makefile (3 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (5 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (6 hunks)
  • internal/broker_consumer/README.md (1 hunks)
  • internal/broker_consumer/consumer.go (3 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/constants.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (6 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • internal/k8s_client/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • pkg/logger/logger_test.go (2 hunks)
  • scripts/build-integration-image.sh (1 hunks)
  • scripts/run-integration-tests.sh (1 hunks)
  • scripts/start-envtest.sh (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/broker_consumer/setup_test.go (1 hunks)
  • test/integration/broker_consumer/testutil_publisher.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (32 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_selector.go (2 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (0 hunks)
  • test/integration/k8s_client/main_test.go (1 hunks)
  • test/integration/testutil/container.go (7 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • test/integration/k8s_client/helper_testcontainers_k3s.go
✅ Files skipped from review due to trivial changes (1)
  • internal/criteria/README.md
🚧 Files skipped from review as they are similar to previous changes (19)
  • scripts/start-envtest.sh
  • internal/k8s_client/client.go
  • internal/criteria/evaluator_test.go
  • cmd/adapter/main.go
  • internal/k8s_client/client_test.go
  • test/testdata/templates/deployment.yaml
  • scripts/run-integration-tests.sh
  • configs/templates/deployment.yaml
  • internal/criteria/types.go
  • internal/config_loader/validator_schema.go
  • configs/README.md
  • configs/templates/job.yaml
  • internal/hyperfleet_api/README.md
  • test/integration/broker_consumer/testutil_publisher.go
  • configs/templates/cluster-status-payload.yaml
  • internal/broker_consumer/README.md
  • internal/config_loader/constants.go
  • internal/hyperfleet_api/types.go
  • internal/hyperfleet_api/client_test.go
🧰 Additional context used
🧬 Code graph analysis (15)
test/integration/k8s_client/client_integration_test.go (1)
test/integration/k8s_client/main_test.go (1)
  • GetSharedEnv (91-96)
test/integration/broker_consumer/adapter_integration_test.go (1)
internal/hyperfleet_api/client.go (1)
  • WithTimeout (66-70)
internal/config_loader/validator.go (4)
internal/config_loader/types.go (3)
  • AdapterConfig (6-11)
  • Condition (130-134)
  • APICall (107-115)
internal/config_loader/constants.go (27)
  • FieldSpec (9-9)
  • FieldPreconditions (19-19)
  • FieldConditions (50-50)
  • FieldAPICall (48-48)
  • FieldURL (57-57)
  • FieldBody (60-60)
  • FieldHeaders (59-59)
  • FieldHeaderValue (65-65)
  • FieldResources (20-20)
  • FieldManifest (78-78)
  • FieldDiscovery (81-81)
  • FieldNamespace (92-92)
  • FieldByName (93-93)
  • FieldBySelectors (94-94)
  • FieldLabelSelector (99-99)
  • FieldPost (21-21)
  • FieldPostActions (104-104)
  • FieldPayloads (41-41)
  • FieldBuild (42-42)
  • FieldExpression (51-51)
  • FieldRef (86-86)
  • FieldRefs (87-87)
  • FieldManifestItems (79-79)
  • FieldAPIVersion (109-109)
  • FieldKind (110-110)
  • FieldMetadata (10-10)
  • FieldName (31-31)
internal/criteria/types.go (6)
  • Operator (8-8)
  • IsValidOperator (42-49)
  • OperatorStrings (52-58)
  • OperatorExists (26-26)
  • OperatorIn (16-16)
  • OperatorNotIn (18-18)
internal/k8s_client/discovery.go (1)
  • Discovery (14-29)
internal/k8s_client/discovery.go (3)
internal/config_loader/types.go (1)
  • DiscoveryConfig (180-184)
internal/hyperfleet_api/types.go (1)
  • Client (216-237)
internal/k8s_client/client.go (1)
  • Client (20-23)
internal/criteria/evaluator_scenarios_test.go (2)
internal/criteria/types.go (9)
  • NewEvaluationContext (67-71)
  • OperatorIn (16-16)
  • OperatorExists (26-26)
  • Operator (8-8)
  • OperatorEquals (12-12)
  • OperatorGreaterThan (22-22)
  • OperatorContains (20-20)
  • OperatorNotIn (18-18)
  • OperatorLessThan (24-24)
internal/criteria/evaluator.go (2)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
internal/config_loader/loader.go (3)
internal/config_loader/types.go (1)
  • AdapterConfig (6-11)
internal/config_loader/validator_schema.go (1)
  • ValidateAdapterVersion (461-473)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/config_loader/loader_test.go (4)
internal/config_loader/loader.go (5)
  • Load (88-111)
  • Parse (114-130)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
  • WithSkipSemanticValidation (62-66)
internal/config_loader/types.go (7)
  • Metadata (14-18)
  • HyperfleetAPIConfig (37-42)
  • AdapterConfig (6-11)
  • AdapterConfigSpec (21-29)
  • Payload (67-78)
  • Resource (170-177)
  • DiscoveryConfig (180-184)
internal/config_loader/accessors.go (1)
  • EnvHyperfleetAPIBaseURL (9-9)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (461-473)
  • IsSupportedAPIVersion (451-458)
internal/criteria/cel_evaluator_test.go (2)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/evaluator.go (1)
  • NewEvaluator (48-55)
internal/criteria/cel_evaluator.go (3)
internal/criteria/types.go (3)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/criteria/evaluator.go (1)
  • ConditionDef (291-295)
internal/config_loader/accessors.go (2)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Parameter (51-58)
  • Resource (170-177)
  • Precondition (98-104)
  • PostAction (198-201)
  • HyperfleetAPIConfig (37-42)
internal/config_loader/constants.go (1)
  • FieldResources (20-20)
test/integration/k8s_client/helper_envtest_prebuilt.go (2)
internal/k8s_client/client.go (2)
  • Client (20-23)
  • NewClientFromConfig (105-115)
test/integration/testutil/container.go (3)
  • ContainerConfig (20-51)
  • StartSharedContainer (403-495)
  • SharedContainer (342-354)
internal/config_loader/types.go (2)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
internal/config_loader/validator_test.go (4)
internal/config_loader/loader.go (1)
  • Parse (114-130)
internal/config_loader/types.go (2)
  • AdapterConfig (6-11)
  • Payload (67-78)
internal/config_loader/validator.go (2)
  • Validate (508-510)
  • ValidationErrors (30-32)
internal/criteria/types.go (1)
  • IsValidOperator (42-49)
internal/hyperfleet_api/client.go (1)
internal/hyperfleet_api/types.go (9)
  • Client (216-237)
  • ClientConfig (38-54)
  • BackoffStrategy (13-13)
  • Request (73-88)
  • Response (159-172)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (91-91)
internal/criteria/evaluator.go (2)
internal/criteria/types.go (4)
  • Operator (8-8)
  • EvaluationContext (61-64)
  • NewEvaluationContext (67-71)
  • EvaluationError (100-104)
internal/criteria/cel_evaluator.go (5)
  • CELEvaluator (16-19)
  • NewCELEvaluator (52-69)
  • CELResult (24-39)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (368-383)
🪛 ast-grep (0.40.0)
test/integration/k8s_client/helper_envtest_prebuilt.go

[warning] 42-44: MinVersionis missing from this TLS configuration. By default, TLS 1.2 is currently used as the minimum when acting as a client, and TLS 1.0 when acting as a server. General purpose web applications should default to TLS 1.3 with all other protocols disabled. Only where it is known that a web server must support legacy clients with unsupported an insecure browsers (such as Internet Explorer 10), it may be necessary to enable TLS 1.0 to provide support. AddMinVersion: tls.VersionTLS13' to the TLS configuration to bump the minimum version to TLS 1.3.
Context: tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
}
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(missing-ssl-minversion-go)

🪛 markdownlint-cli2 (0.18.1)
internal/config_loader/README.md

75-75: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (33)
scripts/build-integration-image.sh (1)

30-30: Improve proxy URL logging for better security posture.

The change removes the actual proxy URL from the logged output, which is a security/privacy improvement—proxy URLs may contain credentials or sensitive configuration. However, this trades away debugging visibility; developers won't see which proxy was selected during build troubleshooting.

The indentation now aligns consistently with surrounding output.

If you'd like to preserve debugging context without exposing sensitive data, consider logging a sanitized version (e.g., hostname only or a masked value).

Makefile (2)

58-58: Error message handling for missing container runtime is clear.

The change to use a single sh -c invocation for error messaging is appropriate and maintains backward compatibility since this code path only executes when no container runtime is available.


92-98: Verify that the referenced bash scripts exist and are executable.

The new integration test targets (image-integration-test and test-integration) depend on bash scripts in the scripts/ directory (build-integration-image.sh and run-integration-tests.sh). These scripts must exist and have executable permissions for the targets to function correctly.

internal/broker_consumer/consumer.go (1)

134-136: Good defensive programming with the nil guard.

The nil check prevents potential nil pointer dereference and provides clear early-return semantics.

configs/broker-configmap-pubsub-template.yaml (1)

24-24: Verify all code paths read the new ConfigMap key names.

The renaming from SUBSCRIPTION_ID/TOPIC to BROKER_SUBSCRIPTION_ID/BROKER_TOPIC is a breaking change. Ensure that all code reading these keys—including the config loader, adapter startup logic, and broker consumer integration—has been updated to reference the new names. Otherwise, the adapter will fail to initialize.

Search the codebase for:

  • Any remaining references to SUBSCRIPTION_ID or TOPIC outside this template file
  • Confirm BROKER_SUBSCRIPTION_ID and BROKER_TOPIC are used consistently in config loading and adapter initialization code
  • Verify no tests or integration paths still reference the old key names

Also applies to: 28-28, 93-93, 97-97

pkg/logger/logger_test.go (1)

266-266: Structure fix looks correct.

The function now properly closes after both chaining subtests.

test/integration/broker_consumer/setup_test.go (1)

100-103: Switch to t.Setenv for env vars is correct and idiomatic

Using t.Setenv here scopes env changes to the test and removes the need for manual restoration; the new setup looks good.

test/integration/broker_consumer/adapter_integration_test.go (1)

95-157: Polling-based publish helper makes the smoke test more robust

The longer context timeout and publishAndWaitForMessage helper remove the fixed-sleep race and give a clear, context-bound way to wait for delivery; handler + buffered channel pattern is safe.

test/integration/k8s_client/main_test.go (1)

38-50: LGTM! Context lifecycle issue has been resolved.

The context cancellation issue raised in the previous review has been fixed. The code now correctly uses defer cancel() on line 40, ensuring the context remains valid for the provider.DaemonHost(ctx) call on line 49.

pkg/errors/api_error.go (1)

16-150: APIError type and helpers look well‑designed and consistent.

The error payload, classification helpers, and constructor cover the key data you’ll want for logging/retries, and the Error() text is clear and structured. I don’t see correctness issues here.

internal/config_loader/loader_test.go (1)

13-1362: Loader/validation test matrix is comprehensive and aligned with the implementation.

These tests exercise happy paths, structural errors, file‑reference handling, discovery constraints, and Condition YAML aliasing in a way that closely matches the validator/loader behavior. I don’t see correctness issues or brittle patterns beyond what’s needed to assert specific error messages.

test/integration/k8s_client/helper_envtest_prebuilt.go (1)

148-155: Envtest setup and default-namespace creation look correct and robust.

The shared container startup, readiness probing via /healthz with bearer auth, and createDefaultNamespaceNoTest helper (ignoring AlreadyExists) give a stable test env without leaking test concerns into helpers that need *testing.T. This structure should work well across integration suites.

Also applies to: 192-210

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

18-446: Integration tests give strong end‑to‑end coverage of config + criteria behavior.

These tests exercise real adapter templates: loading via Load, evaluating preconditions (both per‑condition and combined), converting to/evaluating CEL, probing discovery fields, post‑processing over synthetic K8s resources, and parameter extraction (including the safe HasPrefix source check). The flows look consistent with the runtime APIs and should catch most regressions in config semantics.

go.mod (1)

3-18: Verify Go toolchain compatibility and YAML dependency consolidation strategy.

Go 1.25.0 (released August 2025) is relatively recent. Ensure your local development environments and CI/build images are running compatible Go versions to avoid unexpected toolchain downloads or version mismatches during builds and tests.

Confirm whether your indirect dependencies include multiple YAML packages alongside the direct gopkg.in/yaml.v3 dependency. If so, consider a longer-term consolidation strategy to simplify maintenance and avoid subtle behavioral differences across YAML implementations.

internal/hyperfleet_api/client.go (5)

1-34: LGTM on package setup and client structure.

The imports are clean, environment variable constants are well-defined, and the httpClient struct is properly simplified (rand field correctly removed to use the concurrency-safe package-level rand.Float64()).


36-105: Functional options are well-structured.

The ClientOption pattern is idiomatic Go. The nil check in WithConfig and lazy initialization of DefaultHeaders in WithDefaultHeader are appropriate safeguards.


165-246: Retry logic is robust and well-implemented.

The guard at lines 176-179 ensures at least one attempt is made (addressing the previous review). Context cancellation is properly checked both before attempts and during backoff delays. The APIError wrapping on exhaustion provides good diagnostics.


331-363: Backoff calculation is correct and thread-safe.

The use of package-level rand.Float64() (which uses a locked source) properly addresses the previous data race concern. The jitter calculation and max delay capping are implemented correctly.


369-435: Convenience methods are consistent and well-designed.

All HTTP verb methods follow a uniform pattern, properly delegating to Do() and supporting per-request options. The BaseURL() accessor is a straightforward getter.

internal/config_loader/types.go (5)

1-11: Root configuration structure is well-defined.

The AdapterConfig type follows Kubernetes-style structure with apiVersion, kind, metadata, and spec. YAML tags are correctly applied.


36-42: Configuration fields use string types for flexibility.

Timeout and RetryBackoff as strings allow human-readable YAML values (e.g., "30s", "exponential"). Ensure parsing/validation occurs during config loading.


84-95: Validate() correctly enforces mutual exclusivity.

The logic is clear and error messages are descriptive. Note that hasBuild := p.Build != nil will be true for an empty map/slice (e.g., build: {}), which is likely the intended behavior since it's explicitly set.


144-167: Custom YAML unmarshaling handles value/values duality well.

The UnmarshalYAML implementation correctly rejects configurations with both keys and gracefully handles either key being used. This improves YAML authoring flexibility.


191-206: Post-processing types are well-structured.

PostConfig, PostAction, and ManifestRef are cleanly defined with appropriate YAML tags for serialization.

internal/criteria/evaluator.go (9)

1-55: Evaluator structure with lazy CEL initialization is well-designed.

The use of sync.Once for CEL evaluator caching avoids repeated environment creation overhead. The nil check in NewEvaluator provides a sensible default context.


66-90: Field access methods provide good API ergonomics.

The trio of GetField, GetFieldOrDefault, and GetFieldSafe covers strict, defaulting, and null-safe access patterns. HasField correctly checks both for errors and nil values.


110-163: Condition evaluation with detailed results is well-implemented.

The operator switch is comprehensive, and the negation for NotEquals/NotIn is correctly applied. EvaluationError wrapping provides good diagnostics for field retrieval failures.


327-355: evaluateIn properly guards against nil and invalid values.

The nil check at line 330 and IsValid() check at line 337 address the previous review concern about reflection panics. The error messages are descriptive.


357-424: evaluateContains has thorough nil and validity checks.

All reflection operations are properly guarded:

  • Nil checks for fieldValue and needle at lines 360-367
  • IsValid() checks before Kind() at lines 381-383 and 390-392
  • Item validity check before Interface() at lines 412-414

This addresses the previous review concerns about reflection panics.


440-461: evaluateExists correctly handles various types.

The nil check at line 442 handles the nil interface case. The IsNil() call at line 457 is safe because it's only reached for Ptr or Interface kinds, which support IsNil().


478-508: Numeric conversion covers all standard types.

The type switch handles all integer and float variants. Note that uint64 to float64 conversion may lose precision for values > 2^53, but this is a known Go limitation and acceptable for typical comparison use cases.


510-608: Field traversal is robust with good error reporting.

The recursive traversal handles maps (both map[string]interface{} and map[interface{}]interface{}) and structs. Case-insensitive fallback for struct fields improves usability. FieldNotFoundError provides detailed context for debugging.


621-626: IsFieldNotFound correctly uses errors.As for unwrapping.

This addresses the previous review concern. errors.As properly detects FieldNotFoundError even when wrapped inside EvaluationError.

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: 2

🧹 Nitpick comments (12)
test/integration/broker_consumer/testutil_publisher.go (1)

33-46: Retry on AlreadyExists looks correct; consider minor robustness tweaks

The new immediate retry when isTopicAlreadyExistsError(err) is true makes sense here and should help with racy topic-creation between publisher and subscriber. Using the same evt instance is fine for tests, and failing hard on a second error via require.NoError is appropriate.

Two optional follow-ups you might consider:

  • If the broker ever returns a slightly different error string, isTopicAlreadyExistsError’s substring matching could miss it. If the broker exposes typed errors or status codes, using those instead of string parsing would make this more robust.
  • A very small sleep (e.g., a few milliseconds) before the retry could help if there’s any eventual-consistency window between topic creation and publish being accepted, though the emulator may not need it.

If you’re confident in the broker’s current error format and consistency semantics, the current change is reasonable as-is.

Makefile (1)

58-58: Remove unused CONTAINER_CMD assignment in the "none" case.

The CONTAINER_CMD variable on Line 58 is never executed because the image target (lines 154–162) exits early when CONTAINER_RUNTIME is "none", before attempting to use CONTAINER_CMD. This is dead code.

To simplify, either omit the assignment or add a comment explaining why it's retained for consistency.

- CONTAINER_CMD := sh -c 'echo "No container runtime found. Please install Docker or Podman." && exit 1'
test/integration/k8s_client/helper_selector.go (1)

54-57: contains helper is correct but could be simplified with stdlib helpers

The custom contains / containsSubstring implementation looks correct for all edge cases (including empty substrings and shorter s), and safely guards against out-of-range slicing. Given this is test-only code, you might optionally simplify:

  • Replace contains with strings.Contains and drop containsSubstring, or
  • For isAlreadyExistsError, consider using apierrors.IsAlreadyExists(err) from k8s.io/apimachinery/pkg/api/errors if that dependency is already present elsewhere.

Not required for this PR, but would improve readability and align with common Go/Kubernetes practices.

internal/k8s_client/client_test.go (2)

9-46: Consider adding edge case test for conflicting discovery modes.

The tests cover the primary scenarios well. However, consider adding a test case where both ByName and LabelSelector are set simultaneously to document the expected behavior in this edge case.

Example test case:

t.Run("both byName and labelSelector set", func(t *testing.T) {
    d := &DiscoveryConfig{
        Namespace:     "default",
        ByName:        "my-resource",
        LabelSelector: "app=myapp",
    }
    
    // Document expected behavior: ByName takes precedence
    assert.True(t, d.IsSingleResource())
    assert.Equal(t, "my-resource", d.GetName())
    // LabelSelector may be ignored in single-resource mode
})

48-96: Tests look good; consider adding Kubernetes-style label test.

The table-driven tests are well-structured and cover the core scenarios effectively. The verification of alphabetical sorting is particularly good for deterministic output.

As an optional enhancement, you could add a test case with Kubernetes-style labels containing dots and slashes to ensure proper handling:

{
    name: "kubernetes-style labels with special characters",
    labels: map[string]string{
        "app.kubernetes.io/name":      "myapp",
        "app.kubernetes.io/component": "backend",
        "version":                     "v1.2.3",
    },
    want: "app.kubernetes.io/component=backend,app.kubernetes.io/name=myapp,version=v1.2.3",
},
test/integration/testutil/container.go (1)

161-161: Optional: Extract cleanup timeout constant to reduce duplication.

The 60-second cleanup timeout appears in multiple locations. Consider extracting it to a package-level constant for easier maintenance:

const (
    // MinCleanupTimeout is the minimum timeout for container cleanup operations
    // to ensure containers have sufficient time to stop gracefully.
    MinCleanupTimeout = 60 * time.Second
)

Then reference MinCleanupTimeout at lines 161, 189, 369, and 453.

Also applies to: 189-189, 369-369, 453-453

test/integration/k8s_client/client_integration_test.go (1)

159-159: Minor: Character indexing starts from 'b' instead of 'a'.

string(rune('a'+i)) where i starts at 1 produces 'b', 'c', 'd' rather than 'a', 'b', 'c'. This works for uniqueness but may be unintentional. Consider using 'a'+i-1 or starting the loop from 0 if alphabetical ordering matters.

test/integration/k8s_client/main_test.go (2)

78-84: Type assertion in cleanup should handle failure case.

If sharedEnv implements TestEnv but is not a *TestEnvPrebuilt, the type assertion silently succeeds as false and cleanup is skipped. While currently only TestEnvPrebuilt is used, consider logging when the assertion fails or using an interface method for cleanup.

 	if sharedEnv != nil {
 		println()
 		println("🧹 Cleaning up shared envtest container...")
 		if env, ok := sharedEnv.(*TestEnvPrebuilt); ok {
 			env.CleanupSharedEnv()
+		} else {
+			println("⚠️  Warning: Could not cleanup shared environment (unexpected type)")
 		}
 	}

32-36: Consider clearer skip handling when SKIP_K8S_INTEGRATION_TESTS is set.

When the env var is set, setupErr remains nil but sharedEnv is nil, so GetSharedEnv fails with "Shared test environment is not initialized". This works but the error message doesn't indicate the skip was intentional. Consider using t.Skip() or setting a descriptive setupErr.

 	if os.Getenv("SKIP_K8S_INTEGRATION_TESTS") == "true" {
 		println("⚠️  SKIP_K8S_INTEGRATION_TESTS is set, skipping k8s_client integration tests")
+		setupErr = fmt.Errorf("SKIP_K8S_INTEGRATION_TESTS environment variable is set")
 		os.Exit(m.Run()) // Run tests (they will skip via GetSharedEnv)
 	}
internal/config_loader/accessors.go (1)

15-24: Avoid exposing mutable backing slice from BuiltinVariables

BuiltinVariables returns the package-level builtinVariables slice directly, so callers can mutate it and affect all future validations/templates. Consider returning a copy instead to keep the set of built-ins immutable from the outside.

Example:

 func BuiltinVariables() []string {
-	return builtinVariables
+	out := make([]string, len(builtinVariables))
+	copy(out, builtinVariables)
+	return out
 }
internal/config_loader/validator.go (1)

198-215: Validate templates inside ManifestItems from manifest.refs as well

validateTemplateVariables currently validates inline/single-ref manifests via resource.Manifest, but skips resource.ManifestItems, which hold the content for manifest.refs arrays. Any {{ .var }} templates inside those external manifests won't be checked for undefined variables and may only fail at runtime.

You can extend validation to cover ManifestItems too:

 func (v *Validator) validateTemplateVariables() {
   // Validate resource manifests
   for i, resource := range v.config.Spec.Resources {
     resourcePath := fmt.Sprintf("%s.%s[%d]", FieldSpec, FieldResources, i)
-    if manifest, ok := resource.Manifest.(map[string]interface{}); ok {
-      v.validateTemplateMap(manifest, resourcePath+"."+FieldManifest)
-    }
+    if manifest, ok := resource.Manifest.(map[string]interface{}); ok {
+      v.validateTemplateMap(manifest, resourcePath+"."+FieldManifest)
+    }
+    // Also validate any loaded manifest items from manifest.refs
+    for j, item := range resource.ManifestItems {
+      v.validateTemplateMap(item, fmt.Sprintf("%s.%s[%d]", resourcePath, FieldManifestItems, j))
+    }

This keeps template-variable checking consistent between inline manifests, single-ref, and multi-refs resources.

Also applies to: 461-464

internal/config_loader/loader_test.go (1)

717-853: Consider adding a test for path traversal rejection in file references

You now guard against path traversal in SchemaValidator.resolvePath, but the tests only cover “file exists/does not exist/is directory” under a normal templates/… subtree. It would be useful to add a case where buildRef/manifest.ref uses something like "../outside.yaml" and assert that validation fails with a clear “escapes base directory” error, to lock in the new security behavior.

Also applies to: 1228-1297

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a535220 and 207e14a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (59)
  • Makefile (3 hunks)
  • cmd/adapter/main.go (5 hunks)
  • configs/README.md (2 hunks)
  • configs/adapter-config-template.yaml (5 hunks)
  • configs/broker-configmap-pubsub-template.yaml (2 hunks)
  • configs/templates/cluster-status-payload.yaml (1 hunks)
  • configs/templates/deployment.yaml (1 hunks)
  • configs/templates/job.yaml (1 hunks)
  • go.mod (6 hunks)
  • internal/broker_consumer/README.md (1 hunks)
  • internal/broker_consumer/consumer.go (3 hunks)
  • internal/config_loader/README.md (1 hunks)
  • internal/config_loader/accessors.go (1 hunks)
  • internal/config_loader/constants.go (1 hunks)
  • internal/config_loader/loader.go (1 hunks)
  • internal/config_loader/loader_test.go (1 hunks)
  • internal/config_loader/types.go (1 hunks)
  • internal/config_loader/validator.go (1 hunks)
  • internal/config_loader/validator_schema.go (1 hunks)
  • internal/config_loader/validator_test.go (1 hunks)
  • internal/criteria/README.md (1 hunks)
  • internal/criteria/cel_evaluator.go (1 hunks)
  • internal/criteria/cel_evaluator_test.go (1 hunks)
  • internal/criteria/evaluator.go (1 hunks)
  • internal/criteria/evaluator_scenarios_test.go (1 hunks)
  • internal/criteria/evaluator_test.go (1 hunks)
  • internal/criteria/types.go (1 hunks)
  • internal/hyperfleet_api/README.md (1 hunks)
  • internal/hyperfleet_api/client.go (1 hunks)
  • internal/hyperfleet_api/client_test.go (1 hunks)
  • internal/hyperfleet_api/types.go (1 hunks)
  • internal/k8s_client/README.md (3 hunks)
  • internal/k8s_client/client.go (6 hunks)
  • internal/k8s_client/client_test.go (1 hunks)
  • internal/k8s_client/discovery.go (1 hunks)
  • internal/k8s_client/types.go (1 hunks)
  • pkg/errors/api_error.go (1 hunks)
  • pkg/logger/logger_test.go (2 hunks)
  • scripts/build-integration-image.sh (1 hunks)
  • scripts/run-integration-tests.sh (1 hunks)
  • scripts/start-envtest.sh (1 hunks)
  • test/integration/broker_consumer/adapter_integration_test.go (2 hunks)
  • test/integration/broker_consumer/setup_test.go (1 hunks)
  • test/integration/broker_consumer/testutil_publisher.go (1 hunks)
  • test/integration/config-loader/config_criteria_integration_test.go (1 hunks)
  • test/integration/config-loader/loader_template_test.go (1 hunks)
  • test/integration/config-loader/testdata/adapter-config-template.yaml (1 hunks)
  • test/integration/config-loader/testdata/adapter_config_valid.yaml (1 hunks)
  • test/integration/config-loader/testdata/templates/cluster-status-payload.yaml (1 hunks)
  • test/integration/config-loader/testdata/templates/deployment.yaml (1 hunks)
  • test/integration/config-loader/testdata/templates/job.yaml (1 hunks)
  • test/integration/k8s_client/client_integration_test.go (32 hunks)
  • test/integration/k8s_client/helper_envtest_prebuilt.go (3 hunks)
  • test/integration/k8s_client/helper_selector.go (2 hunks)
  • test/integration/k8s_client/helper_testcontainers_k3s.go (0 hunks)
  • test/integration/k8s_client/main_test.go (1 hunks)
  • test/integration/testutil/container.go (7 hunks)
  • test/testdata/adapter_config_valid.yaml (1 hunks)
  • test/testdata/templates/deployment.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • test/integration/k8s_client/helper_testcontainers_k3s.go
✅ Files skipped from review due to trivial changes (2)
  • internal/criteria/evaluator_scenarios_test.go
  • internal/hyperfleet_api/README.md
🚧 Files skipped from review as they are similar to previous changes (22)
  • pkg/logger/logger_test.go
  • scripts/start-envtest.sh
  • configs/README.md
  • internal/broker_consumer/README.md
  • test/integration/config-loader/loader_template_test.go
  • internal/k8s_client/types.go
  • internal/k8s_client/discovery.go
  • internal/broker_consumer/consumer.go
  • configs/templates/deployment.yaml
  • configs/broker-configmap-pubsub-template.yaml
  • scripts/run-integration-tests.sh
  • internal/config_loader/validator_test.go
  • internal/k8s_client/client.go
  • internal/criteria/README.md
  • cmd/adapter/main.go
  • test/integration/config-loader/config_criteria_integration_test.go
  • internal/config_loader/loader.go
  • internal/k8s_client/README.md
  • test/testdata/templates/deployment.yaml
  • internal/config_loader/constants.go
  • internal/hyperfleet_api/client_test.go
  • internal/criteria/evaluator.go
🧰 Additional context used
🧬 Code graph analysis (11)
internal/k8s_client/client_test.go (2)
internal/config_loader/types.go (1)
  • DiscoveryConfig (180-184)
internal/k8s_client/discovery.go (2)
  • DiscoveryConfig (32-42)
  • BuildLabelSelector (105-122)
internal/criteria/evaluator_test.go (2)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • EvaluationError (100-104)
internal/criteria/evaluator.go (5)
  • NewEvaluator (48-55)
  • ConditionDef (291-295)
  • EvaluationResult (12-23)
  • IsFieldNotFound (623-626)
  • FieldNotFoundError (611-615)
internal/criteria/cel_evaluator_test.go (3)
internal/criteria/types.go (3)
  • NewEvaluationContext (67-71)
  • Operator (8-8)
  • OperatorEquals (12-12)
internal/criteria/cel_evaluator.go (3)
  • NewCELEvaluator (52-69)
  • ConditionToCEL (286-318)
  • ConditionsToCEL (368-383)
internal/criteria/evaluator.go (2)
  • ConditionDef (291-295)
  • NewEvaluator (48-55)
test/integration/k8s_client/main_test.go (2)
test/integration/k8s_client/helper_selector.go (1)
  • TestEnv (15-21)
test/integration/k8s_client/helper_envtest_prebuilt.go (1)
  • TestEnvPrebuilt (80-86)
internal/config_loader/loader_test.go (5)
internal/config_loader/loader.go (4)
  • Load (88-111)
  • Parse (114-130)
  • SupportedAPIVersions (25-27)
  • APIVersionV1Alpha1 (17-17)
internal/config_loader/types.go (9)
  • Metadata (14-18)
  • HyperfleetAPIConfig (37-42)
  • AdapterConfig (6-11)
  • AdapterConfigSpec (21-29)
  • Payload (67-78)
  • Resource (170-177)
  • DiscoveryConfig (180-184)
  • SelectorConfig (187-189)
  • Condition (130-134)
internal/config_loader/accessors.go (1)
  • EnvHyperfleetAPIBaseURL (9-9)
internal/config_loader/validator_schema.go (2)
  • ValidateAdapterVersion (461-473)
  • IsSupportedAPIVersion (451-458)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
test/integration/k8s_client/client_integration_test.go (1)
test/integration/k8s_client/main_test.go (1)
  • GetSharedEnv (91-96)
internal/hyperfleet_api/types.go (1)
internal/k8s_client/client.go (1)
  • ClientConfig (26-35)
internal/config_loader/accessors.go (2)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Parameter (51-58)
  • Resource (170-177)
  • Precondition (98-104)
  • PostAction (198-201)
  • HyperfleetAPIConfig (37-42)
internal/config_loader/constants.go (1)
  • FieldResources (20-20)
internal/hyperfleet_api/client.go (2)
internal/hyperfleet_api/types.go (10)
  • Client (216-237)
  • ClientConfig (38-54)
  • BackoffStrategy (13-13)
  • DefaultClientConfig (57-66)
  • Request (73-88)
  • Response (159-172)
  • BackoffExponential (17-17)
  • BackoffLinear (19-19)
  • BackoffConstant (21-21)
  • RequestOption (91-91)
pkg/errors/api_error.go (1)
  • NewAPIError (123-134)
internal/config_loader/validator_schema.go (4)
internal/config_loader/types.go (6)
  • AdapterConfig (6-11)
  • Metadata (14-18)
  • APICall (107-115)
  • Resource (170-177)
  • SelectorConfig (187-189)
  • Precondition (98-104)
internal/config_loader/loader.go (3)
  • SupportedAPIVersions (25-27)
  • ExpectedKind (18-18)
  • ValidHTTPMethods (30-36)
internal/config_loader/constants.go (25)
  • FieldSpec (9-9)
  • FieldAdapter (15-15)
  • FieldVersion (26-26)
  • FieldParams (18-18)
  • FieldName (31-31)
  • FieldSource (32-32)
  • FieldPreconditions (19-19)
  • FieldAPICall (48-48)
  • FieldExpression (51-51)
  • FieldConditions (50-50)
  • FieldResources (20-20)
  • FieldManifest (78-78)
  • FieldDiscovery (81-81)
  • FieldNamespace (92-92)
  • FieldByName (93-93)
  • FieldBySelectors (94-94)
  • FieldLabelSelector (99-99)
  • FieldPost (21-21)
  • FieldPostActions (104-104)
  • FieldPayloads (41-41)
  • FieldMethod (56-56)
  • FieldURL (57-57)
  • FieldBuildRef (43-43)
  • FieldRef (86-86)
  • FieldRefs (87-87)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/config_loader/types.go (3)
internal/config_loader/validator.go (1)
  • Validate (508-510)
internal/criteria/types.go (1)
  • Operator (8-8)
internal/k8s_client/discovery.go (2)
  • Discovery (14-29)
  • DiscoveryConfig (32-42)
🪛 ast-grep (0.40.0)
test/integration/k8s_client/helper_envtest_prebuilt.go

[warning] 42-44: MinVersionis missing from this TLS configuration. By default, TLS 1.2 is currently used as the minimum when acting as a client, and TLS 1.0 when acting as a server. General purpose web applications should default to TLS 1.3 with all other protocols disabled. Only where it is known that a web server must support legacy clients with unsupported an insecure browsers (such as Internet Explorer 10), it may be necessary to enable TLS 1.0 to provide support. AddMinVersion: tls.VersionTLS13' to the TLS configuration to bump the minimum version to TLS 1.3.
Context: tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
}
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(missing-ssl-minversion-go)

🔇 Additional comments (42)
scripts/build-integration-image.sh (1)

30-30: Good privacy/security practice: avoid logging proxy URLs.

The message now uses a generic placeholder rather than exposing the actual proxy URL. This prevents potentially sensitive configuration from appearing in logs while still signaling proxy detection to the user.

The build args (lines 32–33) correctly pass the actual proxy values to the container build, so functionality is unchanged.

Makefile (2)

92-99: Well-structured integration test targets.

The new .PHONY targets follow existing conventions with clear help text and appropriate delegations to bash scripts. The naming (image-integration-test, test-integration) is consistent with the project's target structure.


117-117: Cache cleaning improves lint consistency.

Running golangci-lint cache clean before each lint execution ensures a fresh baseline, which is a good practice for CI/CD pipelines and local developer workflows.

test/integration/k8s_client/helper_selector.go (1)

23-24: Compile-time interface assertion is idiomatic and helpful

The var _ TestEnv = (*TestEnvPrebuilt)(nil) pattern is a good, compile-time way to ensure TestEnvPrebuilt continues to satisfy the TestEnv interface as things evolve. No issues here.

test/integration/testutil/container.go (8)

9-10: LGTM: Import additions support new CLI cleanup functionality.

The os/exec and strings imports are required for the new forceCleanupContainer and CleanupLeakedContainers functions that use docker/podman CLI as fallback cleanup mechanisms.


39-40: LGTM: CleanupTimeout now consistently defaults to 60s.

The documentation, default configuration, and runtime default now all align at 60s, resolving the inconsistency noted in previous reviews where the default (30s) conflicted with the enforced minimum (60s).

Also applies to: 77-77, 107-107


157-170: LGTM: Retry termination prevents container leaks.

The logic correctly terminates containers that were created but failed to start (e.g., wait strategy timeout), preventing leaks during retry attempts. The 60s termination timeout aligns with the module's documented cleanup minimum.


179-205: LGTM: Robust cleanup with force-removal fallback.

The enhanced cleanup properly captures the container ID before the cleanup lambda, enforces the 60s minimum timeout, and falls back to CLI-based force removal if graceful termination fails. This ensures containers are reliably cleaned up even in failure scenarios.


237-265: LGTM: CLI-based force cleanup with appropriate error handling.

The force cleanup function properly tries both docker and podman runtimes, and now logs all errors (including "not found" cases) as acceptable cleanup noise. This resolves the fragile string-matching concern from previous reviews.


267-305: LGTM: Leaked container cleanup utility.

The function correctly lists containers by image pattern and removes them across docker/podman runtimes. The silent continuation when a runtime isn't available (line 282) is appropriate for a best-effort cleanup utility.


340-495: LGTM: SharedContainer design enables efficient test resource sharing.

The SharedContainer type and StartSharedContainer function provide a clean pattern for sharing containers across tests in TestMain. Key design choices:

  • No automatic cleanup registration (caller must invoke Cleanup() explicitly) — correct for TestMain usage
  • MaxRetries defaults to 3 (vs. 1 for regular containers) — reasonable for shared infrastructure
  • println for logging — acceptable since testing.T isn't available in TestMain context

497-515: LGTM: TestMain-compatible force cleanup helper.

The forceCleanupContainerNoTest function appropriately mirrors forceCleanupContainer but uses println instead of t.Logf, making it suitable for cleanup in TestMain where testing.T isn't available.

test/integration/k8s_client/client_integration_test.go (2)

16-30: Local GVK definitions are clean and well-structured.

Defining common GroupVersionKinds locally avoids coupling to test-only exports while keeping the test file self-contained. The struct approach with named fields improves readability over separate constants.


34-34: Consistent use of shared environment pattern.

The switch from per-test setup to GetSharedEnv(t) is applied consistently across all test functions, improving test execution time by reusing a single envtest container. The pattern correctly uses t.Helper() in the accessor function.

test/integration/k8s_client/main_test.go (1)

38-71: Context lifecycle is now correct.

The previous issue with cancel() being called before provider.DaemonHost(ctx) has been properly addressed. The context is now correctly managed with defer cancel() after creation.

test/integration/k8s_client/helper_envtest_prebuilt.go (4)

42-46: Static analysis hint about TLS MinVersion is acceptable here.

The static analyzer flags the missing MinVersion in the TLS config. However, since this is test code connecting to a local envtest container with InsecureSkipVerify: true already set (acknowledged by the nolint comment), the security implications are negligible. The existing nolint:gosec comment appropriately justifies the trade-off.


88-92: Clear documentation for no-op Cleanup method.

The comment clearly explains why Cleanup is a no-op for shared containers and directs users to CleanupSharedEnv. This is a good pattern for shared test resources.


94-177: Robust error handling with proper cleanup on failures.

setupSharedTestEnv correctly cleans up the container on failure paths (API server readiness check, client creation). Returning errors instead of panicking allows graceful handling in TestMain.


204-208: Verify isAlreadyExistsError helper is defined.

The function isAlreadyExistsError is called on line 206 but not visible in this file. Ensure this helper is defined elsewhere in the package or imported; otherwise this will cause a compilation error.

test/integration/config-loader/testdata/templates/job.yaml (1)

1-29: Test template structure looks good.

The Job manifest template properly includes all required Kubernetes fields, labels for discovery (hyperfleet.io/resource-type, hyperfleet.io/managed-by), and environment variable templating. Aligns with production template expectations.

test/integration/config-loader/testdata/templates/deployment.yaml (1)

1-37: Deployment template structure is sound.

The template includes proper Kubernetes Deployment structure with consistent label selectors, resource constraints, and templating for cluster-scoped naming. Resource requests/limits are appropriately defined for a controller workload.

configs/templates/job.yaml (1)

1-29: Production Job template is complete and correct.

All required discovery labels are present (hyperfleet.io/resource-type: "job" and hyperfleet.io/managed-by), addressing previous review feedback. Template structure is sound for integration with the adapter's resource discovery mechanism.

test/integration/config-loader/testdata/templates/cluster-status-payload.yaml (1)

1-16: Test payload template structure is appropriate.

The template provides a proper structure for cluster status payloads with condition tracking and default fallbacks. Aligns with standard Kubernetes condition patterns and integrates with payload templating tests.

configs/templates/cluster-status-payload.yaml (1)

1-16: Payload template is well-structured.

The cluster status payload template uses RFC3339 date formatting, provides sensible condition defaults, and follows Kubernetes patterns for condition reporting. Template design supports robust payload generation even when some context fields are missing.

go.mod (1)

9-9: CEL and Kubernetes dependencies properly added.

The go.mod file appropriately includes CEL-related libraries (github.com/google/cel-go v0.26.1, cel.dev/expr, github.com/antlr4-go/antlr/v4) for criteria evaluation, and existing Kubernetes/YAML dependencies are already in place. Direct dependency on CEL-Go has been updated to v0.26.1 per previous review feedback.

Also applies to: 21-21, 34-34, 93-93, 103-103, 119-119, 134-134

test/integration/config-loader/testdata/adapter_config_valid.yaml (4)

1-50: Adapter metadata and parameters are well-structured.

Configuration properly defines required environment parameters (API credentials) and event-sourced parameters (clusterId, resourceId) with appropriate required and source attributes. Follows the expected schema for adapter config.


51-87: Preconditions demonstrate proper CEL and API integration.

The clusterStatus precondition correctly captures response fields and validates using structured conditions with valid operators (in, exists). The validationCheck precondition shows valid CEL expression syntax for post-capture validation.


88-132: Resource manifests and discovery rules are appropriately configured.

Resources include inline manifests, external template references, and flexible discovery patterns (bySelectors, byName). Templating consistently uses clusterId, clusterName, and region variables. Labels enable resource discovery per the adapter framework expectations.


133-196: Post-processing configuration demonstrates CEL condition evaluation.

Payloads are built using CEL expressions for applied/available/health conditions, with safe field access patterns (has(), null checks). Post-actions correctly reference the computed payload and include proper HTTP headers (Authorization Bearer token). Configuration demonstrates end-to-end adapter workflow: parameter extraction → precondition validation → resource provisioning → status reporting.

internal/criteria/types.go (1)

10-116: Operator and evaluation context definitions look solid

The operator enum, helper functions (IsValidOperator, OperatorStrings), EvaluationContext, and EvaluationError are cohesive and straightforward. They align well with how conditions and CEL evaluation are used elsewhere.

internal/criteria/cel_evaluator.go (1)

51-209: CEL evaluator and condition-to-CEL conversion look robust

Dynamic env construction, EvaluateSafe error normalization, and ConditionToCEL operator handling are consistent and well-documented. The behavior around missing fields (errors surfaced via CELResult) is clearly described in comments and matches the intended usage.

Also applies to: 282-383

internal/hyperfleet_api/types.go (1)

24-66: HTTP client types and defaults are well-structured

ClientConfig, Request/Response, and the Client interface line up cleanly with the implementation in client.go, with sensible defaults for timeouts and retries and clear helper methods for status classification.

Also applies to: 72-237

internal/config_loader/validator_schema.go (1)

268-331: Schema validation and path resolution look consistent and safe

The structural validators plus validateFileReferences/loadFileReferences form a clear pipeline, and resolvePath’s use of filepath.Abs + filepath.Rel to reject paths that escape baseDir is a solid hardening step. The legacy wrapper functions keep the existing loader API intact while routing through the new logic.

Also applies to: 404-434

internal/hyperfleet_api/client.go (1)

171-179: Clarify behavior when client/request timeout is zero to avoid immediate cancellation

doRequest unconditionally wraps the parent context with context.WithTimeout, even when the effective timeout is zero:

timeout := c.config.Timeout
if req.Timeout > 0 {
    timeout = req.Timeout
}
// ...
reqCtx, cancel := context.WithTimeout(ctx, timeout)

In Go's standard library, context.WithTimeout with a zero or negative duration immediately returns a context with an already-expired deadline (triggering context.DeadlineExceeded right away). If a caller supplies a custom ClientConfig with Timeout == 0, this results in immediate cancellation rather than "no timeout", which is likely surprising.

Consider treating non-positive timeouts as "no additional deadline" and only wrapping when timeout > 0:

- timeout := c.config.Timeout
- if req.Timeout > 0 {
-     timeout = req.Timeout
- }
-
- // Create context with timeout
- reqCtx, cancel := context.WithTimeout(ctx, timeout)
- defer cancel()
+ timeout := c.config.Timeout
+ if req.Timeout > 0 {
+     timeout = req.Timeout
+ }
+
+ // Create request context; only add a deadline when timeout > 0
+ reqCtx := ctx
+ var cancel context.CancelFunc
+ if timeout > 0 {
+     reqCtx, cancel = context.WithTimeout(ctx, timeout)
+     defer cancel()
+ }

This keeps the HTTP client usable with "no-timeout" configs while still enforcing deadlines when configured.

Also applies to: 264-275

internal/config_loader/types.go (8)

1-47: LGTM!

The core configuration structures are well-defined with appropriate YAML tags. The design correctly makes BaseURL optional (allowing environment variable override) while requiring timeout and retry settings to have explicit values or defaults.


49-58: LGTM!

The Parameter struct appropriately models configuration inputs with flexible default value typing.


60-95: LGTM!

Excellent validation logic enforcing Build/BuildRef mutual exclusivity with clear error messages. The design correctly separates config-sourced fields from loader-populated fields.


97-127: LGTM!

The precondition and API call structures provide appropriate flexibility for both structured conditions and raw expressions.


129-167: LGTM!

The custom UnmarshalYAML implementation correctly handles both "value" and "values" keys while enforcing mutual exclusivity. The yaml:"-" tag on Value is appropriate since it's populated from either source key during unmarshaling.


169-177: LGTM!

The Resource structure appropriately uses interface{} for the Manifest field to accommodate diverse Kubernetes resource definitions, and correctly separates loader-populated fields.


191-206: LGTM!

The post-processing configuration structures are clear and appropriately compose the payload and action types defined earlier.


179-189: Consider renaming to avoid type name collision.

Both internal/config_loader and internal/k8s_client export a DiscoveryConfig type with similar but distinct structures. While the separation may be intentional (config parsing vs runtime interface), the identical names can cause confusion when both packages are imported together, requiring import aliases and increasing cognitive load during code navigation.

Consider renaming one type for clarity (e.g., ResourceDiscoveryConfig in this package or K8sDiscoveryConfig in k8s_client) or consolidating if the types can be unified.

Comment on lines +74 to +78
Validation errors are descriptive:
```
spec.params[0].name is required
spec.preconditions[1].apiCall.method must be one of: GET, POST, PUT, PATCH, DELETE
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifier to validation error code block.

The fenced code block is missing a language identifier, which triggers markdownlint (MD040). This was flagged in previous reviews.

Apply this diff to fix the code fence:

 Validation errors are descriptive:
-```
+```text
 spec.params[0].name is required
 spec.preconditions[1].apiCall.method must be one of: GET, POST, PUT, PATCH, DELETE
-```
+```
🤖 Prompt for AI Agents
internal/config_loader/README.md around lines 74 to 78: the fenced code block
showing validation errors lacks a language specifier which triggers markdownlint
MD040; update the opening fence to use a language identifier (e.g., text) so it
reads ```text instead of ```, leaving the block contents unchanged and keeping
the closing fence as ``` to satisfy the linter.

Comment on lines +38 to +77
func waitForAPIServerReady(kubeAPIServer string, timeout time.Duration) error {
// Create HTTP client with TLS verification disabled (for self-signed certs)
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
},
},
}

healthURL := kubeAPIServer + "/healthz"
deadline := time.Now().Add(timeout)
backoff := 500 * time.Millisecond

for {
req, err := http.NewRequest(http.MethodGet, healthURL, nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+EnvtestBearerToken)

resp, err := client.Do(req)
if err == nil {
_ = resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return nil
}
}

if time.Now().After(deadline) {
if err != nil {
return fmt.Errorf("API server not ready after %v: last error: %w", timeout, err)
}
return fmt.Errorf("API server not ready after %v: last status code: %d", timeout, resp.StatusCode)
}

time.Sleep(backoff)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential stale resp reference in timeout error message.

When the deadline is exceeded, if the last iteration had an HTTP error (err != nil), the code correctly reports the error. However, if err was nil but resp.StatusCode != 200, the loop continues and resp is reused. On the next iteration, if there's a connection error, resp from the previous iteration is used in the error message at line 72, which could be misleading.

Consider tracking the last response status separately:

 func waitForAPIServerReady(kubeAPIServer string, timeout time.Duration) error {
 	// ... client setup ...
 
 	healthURL := kubeAPIServer + "/healthz"
 	deadline := time.Now().Add(timeout)
 	backoff := 500 * time.Millisecond
+	var lastStatusCode int
+	var lastErr error
 
 	for {
 		req, err := http.NewRequest(http.MethodGet, healthURL, nil)
 		if err != nil {
 			return fmt.Errorf("failed to create request: %w", err)
 		}
 		req.Header.Set("Authorization", "Bearer "+EnvtestBearerToken)
 
 		resp, err := client.Do(req)
 		if err == nil {
 			_ = resp.Body.Close()
 			if resp.StatusCode == http.StatusOK {
 				return nil
 			}
+			lastStatusCode = resp.StatusCode
+			lastErr = nil
+		} else {
+			lastErr = err
 		}
 
 		if time.Now().After(deadline) {
-			if err != nil {
-				return fmt.Errorf("API server not ready after %v: last error: %w", timeout, err)
+			if lastErr != nil {
+				return fmt.Errorf("API server not ready after %v: last error: %w", timeout, lastErr)
 			}
-			return fmt.Errorf("API server not ready after %v: last status code: %d", timeout, resp.StatusCode)
+			return fmt.Errorf("API server not ready after %v: last status code: %d", timeout, lastStatusCode)
 		}
 
 		time.Sleep(backoff)
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func waitForAPIServerReady(kubeAPIServer string, timeout time.Duration) error {
// Create HTTP client with TLS verification disabled (for self-signed certs)
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
},
},
}
healthURL := kubeAPIServer + "/healthz"
deadline := time.Now().Add(timeout)
backoff := 500 * time.Millisecond
for {
req, err := http.NewRequest(http.MethodGet, healthURL, nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+EnvtestBearerToken)
resp, err := client.Do(req)
if err == nil {
_ = resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return nil
}
}
if time.Now().After(deadline) {
if err != nil {
return fmt.Errorf("API server not ready after %v: last error: %w", timeout, err)
}
return fmt.Errorf("API server not ready after %v: last status code: %d", timeout, resp.StatusCode)
}
time.Sleep(backoff)
}
}
func waitForAPIServerReady(kubeAPIServer string, timeout time.Duration) error {
// Create HTTP client with TLS verification disabled (for self-signed certs)
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
},
},
}
healthURL := kubeAPIServer + "/healthz"
deadline := time.Now().Add(timeout)
backoff := 500 * time.Millisecond
var lastStatusCode int
var lastErr error
for {
req, err := http.NewRequest(http.MethodGet, healthURL, nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+EnvtestBearerToken)
resp, err := client.Do(req)
if err == nil {
_ = resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return nil
}
lastStatusCode = resp.StatusCode
lastErr = nil
} else {
lastErr = err
}
if time.Now().After(deadline) {
if lastErr != nil {
return fmt.Errorf("API server not ready after %v: last error: %w", timeout, lastErr)
}
return fmt.Errorf("API server not ready after %v: last status code: %d", timeout, lastStatusCode)
}
time.Sleep(backoff)
}
}
🧰 Tools
🪛 ast-grep (0.40.0)

[warning] 42-44: MinVersionis missing from this TLS configuration. By default, TLS 1.2 is currently used as the minimum when acting as a client, and TLS 1.0 when acting as a server. General purpose web applications should default to TLS 1.3 with all other protocols disabled. Only where it is known that a web server must support legacy clients with unsupported an insecure browsers (such as Internet Explorer 10), it may be necessary to enable TLS 1.0 to provide support. AddMinVersion: tls.VersionTLS13' to the TLS configuration to bump the minimum version to TLS 1.3.
Context: tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Required for envtest self-signed certs
}
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(missing-ssl-minversion-go)

🤖 Prompt for AI Agents
In test/integration/k8s_client/helper_envtest_prebuilt.go around lines 38 to 77,
the timeout error message can reference a stale resp variable; instead track the
last observed HTTP status in a local int (e.g. lastStatus initialized to -1) and
update it whenever resp != nil (set lastStatus = resp.StatusCode before closing
body), then when the deadline is exceeded use lastStatus in the error message
rather than resp.StatusCode; also preserve the existing behavior of returning
the underlying err when err != nil.

@openshift-ci
Copy link

openshift-ci bot commented Dec 3, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rh-amarin, xueli181114

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:
  • OWNERS [rh-amarin,xueli181114]

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

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