-
Notifications
You must be signed in to change notification settings - Fork 8
feat: implement config loader, criteria evaluator, and API client #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds 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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~150 minutes Files/areas needing extra attention:
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this 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_IDkey. Update it to useBROKER_SUBSCRIPTION_IDto 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
:latestfor 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
bodyboth as the second argument toPostand viaWithJSONBody(body). This could confuse users about the intended usage. IfPostalready accepts a body argument,WithJSONBodymay be redundant or override it.Consider clarifying whether
WithJSONBodysets 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: Preferstrings.Containsover custom implementation.The custom
containsandcontainsHelperfunctions replicatestrings.Containsfrom 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: Preferio.ReadAllover custom implementation.The custom
readAllfunction can be replaced withio.ReadAllfrom 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
validateFileExistsfunction resolves paths but doesn't validate that the resolved path stays within the expected directory. A malicious config could reference../../etc/passwdor 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:resolvePathshould usefilepath.Cleanto normalize paths.While
filepath.Joinhandles some normalization, explicitly cleaning the result helps prevent path traversal via sequences likefoo/../../../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
SchemaValidatorinstance with an emptybaseDir. 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., accessc.configwithout nil checks. IfNewClientis modified or options are applied in unexpected ways, this could panic.Consider initializing config before applying options, which is already done in
NewClientat line 123. Document that options should only be used withNewClient.
108-118: Silent no-op when HYPERFLEET_API_TOKEN is empty.
WithBearerTokenFromEnvsilently 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 delayinternal/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
[]stringcase duplicates the string escaping logic from thestringcase above. Consider usingformatCELValuerecursively 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, ", ")), nilinternal/config_loader/types.go (1)
63-69: Inconsistent field naming:observed_generationuses snake_case.The field
ObservedGenerationhas a YAML tagobserved_generation(snake_case) while other fields likelastUpdateduse 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:existsoperator withvalue: truemay be redundant.The
existsoperator typically doesn't need a value parameter - its purpose is to check field existence. Thevalue: truemay be ignored or could cause confusion.Consider simplifying:
- field: "vpcId" operator: "exists" - value: trueinternal/criteria/evaluator.go (2)
215-240: CEL evaluator instantiated on every call - consider caching.Each
EvaluateCEL*method creates a newCELEvaluatorinstance. 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 inEvaluateConditionSafe.Lines 94-97 return
falsefor field not found, then line 97 also returnsfalse. The second return is unreachable from theIsFieldNotFoundbranch 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
⛔ Files ignored due to path filters (1)
-
go.sumis 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:latestis 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: 3andrestartPolicy: Neversettings 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
nowanddatefunctions. 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/dockertogithub.com/docker/go-connectionsas 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.
IsValidOperatorandOperatorStringsprovide clean validation and conversion utilities. The linear search inIsValidOperatoris appropriate given the small number of operators.
89-94: Note: Merge performs shallow copy.The
Mergemethod 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.
EvaluationErrorcorrectly implements theerrorinterface withUnwrap()support for error chain compatibility.
85-87: Manual verification needed: ConfirmgetNestedFieldhelper is defined.
GetNestedFieldcallsgetNestedField(c.Data, path), but this helper function could not be located through automated search. Please confirm this function is properly defined elsewhere in theinternal/criteriapackage (likely in a sibling file such asevaluator.goorhelpers.go) and is accessible totypes.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
Domethod 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 toSUBSCRIPTION_IDandTOPICclearly 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
createAPIClientfunction 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
TestRealWorldScenariotest provides excellent coverage of precondition evaluation patterns that match the adapter config template. The tests properly validate individual conditions and combined evaluation usingEvaluateConditions.
220-299: Well-structured table-driven test for node count validation.The test correctly uses
OperatorGreaterThan(minNodes-1)to simulate>=andOperatorLessThan(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.AddInt32for 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-clientexists as a related package (referenced on line 362 of the README). Without access to the codebase, I cannot definitively determine whether:
- The
internal/k8s-clientpackage exists- The reference is accurate or planned for future implementation
- 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
assertandrequirepackages 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
storeResponseAswithextractfields 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.nameis 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.Postprevent 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, nilwhen timeout is empty, allowing callers to apply defaults. Uses standardtime.ParseDurationfor 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, nilfor 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.Isanderrors.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.v3does 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:
- CVE-2022-28948 / GO-2022-0603 — older versions (< v3.0.1) can panic/crash on malformed input (DoS risk)
- The actual risks with untrusted YAML are DoS and resource exhaustion, not RCE
- 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.v3library 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
ValidationErrorandValidationErrorstypes provide good structured error reporting with path context. TheAddhelper 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
.varNamestyle references, or should broader template syntax be supported?
280-316: CEL environment initialization uses DynType appropriately.Using
cel.DynTypefor 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 ofmath/randhas been confirmed.However, I cannot fully verify the review comment because:
- Repository access failed - I cannot examine the actual usage of
math/randin the codebase- No context on Go version - Cannot determine the project's Go version requirements
- No usage verification - Cannot confirm if
math/randis actually being used for jitter/backoff, or if it's even used at all in this fileGiven the constraints, here's the rewritten comment:
Verify if
math/randusage requirescrypto/randbased on Go version and use case.The comment assumes
math/randis used for jitter, but actual usage context is needed. Go 1.20+ auto-seeds the globalmath/randgenerator at startup, making it acceptable for non-cryptographic use cases like backoff timing without explicit seeding. If the project targets Go <1.20 or usesmath/randfor security-sensitive operations, considercrypto/randinstead. Confirm the project's Go version requirement and the specific purpose of these imports.internal/criteria/cel_evaluator.go (3)
91-100: Empty expression returnstrue- verify this is intentional.When an empty expression is passed to
Evaluate, it returnsMatched: 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
NewCELEvaluatorfunction 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'shas()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)returnsfalserather than raising an error. This is the explicit design ofhas(): to safely check field presence when fields or intermediates may be absent. The current code at lines 224-241 correctly generateshas(parent.lastField)for nested fields, which is valid CEL syntax and handles the reviewer's concern automatically. The suggestion to chain multiplehas()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
ParseDurationis used to validate these string fields- How
HyperfleetAPIConfigis 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.
TimeoutandRetryBackoffare string types. Ensure there's consistent parsing/validation elsewhere (e.g., usingtime.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/bashSearch 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/nullecho -e "\n=== Checking for any comment in YAML files ==="
head -50 test/testdata/adapter_config_valid.yamlecho -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/bashCheck for any top-level documentation or setup files
echo "=== Listing all files in repository root ==="
ls -laecho -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/bashCheck 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 -100echo -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/bashCheck 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/nullecho -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/bashLet'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 -baecho -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:
- Repository contains only test data - No Go implementation code, evaluator logic, or CEL context setup code exists in this repository
- 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
- Filename suggests validity - The file is named
adapter_config_valid.yaml, implying this configuration should be valid as-is- No visible variable registration - The expressions assume
resourcesexists in the evaluation context, but the code that provides this context is not in this repositoryThe 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
resourcesvariable is properly registered (review comment invalid)- The
resourcesvariable is not registered (review comment valid)
CEL expressions reference the
resourcesvariable. 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
NewEvaluatorfunction properly initializes an empty context when nil is passed, consistent with the pattern used inNewCELEvaluator.
277-289: LGTM - Null-safe equality evaluation.The
evaluateEqualsfunction correctly handles nil cases before usingreflect.DeepEqual.
515-530: LGTM - Custom error type with helper function.The
FieldNotFoundErrortype andIsFieldNotFoundhelper provide a clean pattern for error handling in field traversal.
51-53: I'll help you verify whether theGetNestedFieldmethod exists onEvaluationContext. Let me search the codebase for this method and the type definition.
<function_calls>
#!/bin/bashSearch for GetNestedField method definition
ast-grep --pattern 'func (
$_ *EvaluationContext) GetNestedField($ ) ($) {
$$$
}'
</function_calls>
#!/bin/bashAlso search with rg to see all references to GetNestedField
rg -n 'GetNestedField' --type go
</function_calls>
#!/bin/bashSearch for EvaluationContext type definition
rg -n 'type EvaluationContext' --type go -A 5
</function_calls>
<function_calls>
#!/bin/bashCheck 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
getNestedFieldis truly unused. Multiple verification attempts failed during repository cloning. Manual inspection of the code would be required to:
- Confirm whether
getNestedFieldis called anywhere in the codebase- Verify the relationship between the standalone
getNestedFieldfunction andEvaluationContext.GetNestedField()- Determine if this represents actual code duplication or if both functions serve distinct purposes
270-275: Manual verification of theConditionDefduplicate definition claim is required.Unable to access the repository to verify whether
ConditionDefis defined in bothinternal/criteria/evaluator.goand referenced without proper import handling incel_evaluator.go. The codebase could not be cloned for automated analysis.To complete this verification:
- Confirm
ConditionDefis defined exactly once across the codebase- Verify
cel_evaluator.goproperly imports or referencesConditionDeffrom the correct location- Check compilation succeeds without duplicate definition errors
987308f to
1680106
Compare
There was a problem hiding this 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 informatCELValueis still incomplete; usestrconv.Quotefor correctnessOnly double quotes are escaped for
stringand[]stringvalues, 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 reusingformatCELValuefor[]stringelements:-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, ", ")), nilYou 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 solidThe added logic to terminate a partially created container on a failed start attempt (and fall back to
forceCleanupContainerwhenTerminatefails) is a good robustness improvement that should significantly reduce leaked containers between retries. One optional nicety would be to setcontainer = nilafter 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 docsCapturing
containerIDandcontainerNamefor logging, plus enforcing a minimum 60s timeout for cleanup with an ID-basedforceCleanupContainerfallback, makes teardown much more resilient. One thing to double‑check is the comment onContainerConfig.CleanupTimeout(“default: 30s”): sinceStartContainernow enforcescleanupTimeout >= 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 testsBoth
forceCleanupContainerandCleanupLeakedContainersuse bareexec.Commandinvocations. If thedocker/podmanCLI 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.CommandContextfor both thepsandrm -fcommands (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 terminateSwitching
TestEnvPrebuilt.Cleanupto 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 sete.Container = nilso 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 tweaksThe
setupSuccess+deferpattern 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 != nilcheck in addition toresult.Container != nilto be maximally defensive against future changes toStartContainer’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.Cleanupregistered insideStartContainer(assumingTerminateremains idempotent).Also applies to: 186-186
test/integration/k8s_client/helper_testcontainers_k3s.go (1)
95-106: Earlyt.Cleanupregistration is a solid leak-prevention improvementRegistering
t.Cleanupimmediately afterk3s.Runis a good move and ensures the container is torn down even if laterrequirecalls abort the test setup. Two minor, optional refinements you might consider:
- The outer
if k3sContainer != nilplus 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., viat.Logforlog.Infof) so you get a signal when cleanup is not fully successful.Also, with this implicit
t.Cleanupin place and the existing(*TestEnvTestcontainers).Cleanupmethod still available, ensure tests don’t routinely call both paths for the same instance (or make the explicitCleanupidempotent) to avoid noisy doubleTerminateattempts.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 tocel.DynType. That’s fine functionally, but you could get better type‑checking and editor feedback by treating any slice/array (detected via reflection) ascel.ListType(cel.DynType)or a more specific element type.
198-241:existsoperator behavior is reasonable but slightly opinionatedThe
existsoperator maps tohas(parent.field)for dotted paths andx != 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
existsto be used on non‑string fields or map keys, it may be worth tightening the semantics (e.g., only!= nullfor non‑strings, or usinghas(m[key])for maps).internal/criteria/types.go (1)
60-94: Consider makingEvaluationContextmore defensive around nil maps
NewEvaluationContextinitializesData, but if a caller constructs anEvaluationContextmanually (or deserializes one) withData == nil,Set/Mergewill 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 tagThe labels, selectors, namespace, and
CLUSTER_ID/RESOURCE_IDenv 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
:latestto 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 tagThe 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:latesttag 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
createAPIClientis 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
IsAPIErrorfunction name suggests a boolean return (likeerrors.Is), but it returns(*APIError, bool)similar toerrors.As. While the docstring clarifies this, consider using onlyAsAPIErrorand removingIsAPIErrorto avoid potential confusion, or keep both but document thatAsAPIErroris 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/templateparser 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 + _ = checkIssuesinternal/config_loader/validator_schema.go (2)
408-414: Consider path traversal validation.The
resolvePathfunction joins the baseDir with the refPath directly. Whilefilepath.IsAbsprevents absolute paths from bypassing, relative paths like../../../etc/passwdcould 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
⛔ Files ignored due to path filters (1)
go.sumis 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-connectionsanddocker/dockerare 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/dockerclient APIs (theClienttype 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/dockerhas been replaced withdocker/go-connections. These are not functionally equivalent:docker/go-connectionsprovides only low-level networking utilities (sockets, dialers, TLS, URL helpers), whiledocker/dockeris the full Docker Engine API client. If existing code usesdocker/docker'sClienttype or high-level API methods (ContainerList,ImagePull, etc.), this change will cause build failures. Verify that the codebase does not depend ondocker/dockerAPIs, or restoredocker/dockeras 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/v3is "likely a typo" and should begopkg.in/yaml.v3. However, web search results confirm thatgo.yaml.in/yaml/v3is a legitimate, real Go module—it's the YAML organization's maintained fork/alternative version, distinct from the originalgopkg.in/yaml.v3.What I verified:
- ✅
go.yaml.in/yaml/v3exists as a real, maintained module (not a typo)- ✅
gopkg.in/yaml.v3is a separate module with v3.0.1 available- ✅
v3.0.4is not found in standard proxy listings forgopkg.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/v3module- 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 configThe example, env var table, and explanatory note now align on
BROKER_SUBSCRIPTION_IDand clearly document its relationship toBROKER_GOOGLEPUBSUB_SUBSCRIPTION. No further adjustments needed here.OWNERS (1)
1-16: Quoting numeric approver ID is a good YAML hygiene improvementQuoting
"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 solidBuilding the CEL environment dynamically from
EvaluationContext.DatawithinferCELTypeis 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‑structuredThe parse → (best‑effort) check → program → eval flow, plus the empty‑expression shortcut to a
trueresult, reads cleanly. TheCELResult.Matchedsemantics (strict for bools, “non‑empty” for other values viaisEmptyValue) 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 completeThe
Operatorconstants,SupportedOperators,IsValidOperator, andOperatorStringsprovide 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 debuggingThe error message includes both the field name and a human message, and
Unwrappreserves 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 operatorsThe 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
Operatorset andEvaluationContext/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 unverifiedThe review recommends scanning for remaining uses of the legacy
SUBSCRIPTION_IDandTOPICkeys to ensure the rename toBROKER_SUBSCRIPTION_IDandBROKER_TOPICis 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/TOPICkeys 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
createAPIClientfunction 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
TestValidateConditionOperatorstest 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
criteriapackage. This helps catch regressions if operator support changes.
592-602: LGTM!Good test for
ValidationErrorsaggregation 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
APIErrorstruct is well-designed with comprehensive context fields. TheError()method provides clear, informative messages with conditional formatting based on whether a status code is present, andUnwrap()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 andcontext.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
EvaluationContextlifecycle 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
Evaluatorcovering 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
CELEvaluatorverifying 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
EvaluateBoolandEvaluateString.
175-260: LGTM!Comprehensive test coverage for
ConditionToCELcovering all operators and verifying the generated CEL expression format.
262-278: LGTM!Good test for
ConditionsToCELverifying multiple conditions are joined with&&and empty conditions return"true".
280-311: LGTM!Good integration test verifying the
Evaluatorcorrectly delegates CEL evaluation methods.
313-330: LGTM!Good test for CEL expression generation methods.
332-355: LGTM!Good coverage for
formatCELValueincluding 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/assertandtestify/requirefollows 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, andadapter.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, andpost.paramsreferences. 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 := rangeand 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 allGetXxxByNamemethods.
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 singlerefandrefsarray 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
ValidHTTPMethodsmap 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 offilepath.Absensures consistent path resolution.
129-139: Deprecated wrappers maintained for backward compatibility.The
LoadWithVersionandParseWithVersionfunctions 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:
- Core structural validators (always)
- Adapter version validation (if version provided)
- File reference validation and loading (if baseDir set)
- 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
ValidationErrorandValidationErrorstypes 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
addedRootsprevents duplicate variable declarations. Usingcel.DynTypefor 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 thatmetadata.nameis present. The empty string checks forapiVersionandkindare 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 andValidateFileReferences()/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
validateFileExistsmethod 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
loadFileReferencesmethod correctly:
- Handles both single and multiple refs
- Maintains backward compatibility by replacing
Manifestfor single refs- Uses
ManifestItemsfor multiple refs- Provides clear error paths with indices
466-500: Lightweight backward compatibility wrappers.The legacy functions create new
SchemaValidatorinstances 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 consistentThe
Get/Post/Put/Patch/Deletehelpers are thin, predictable wrappers that construct aRequest, applyRequestOptions, and delegate toDo. 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'scontext.WithTimeout(ctx, 0)does indeed create an immediately-expired context because it sets the deadline totime.Now().Add(0), causing the context'sDonechannel to close immediately andErr()to returncontext.DeadlineExceeded.However, I cannot verify:
- Whether the code at lines 242-250 in
internal/hyperfleet_api/client.goactually matches the provided snippet- Whether the
timeoutvariable can realistically be 0 in the codebase's execution paths- 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.WithTimeoutwith zero timeout is sound per Go documentation. However, without access to the codebase, I cannot confirm whether:
- The code currently exhibits this pattern
- The
timeoutparameter can actually reach zero during execution- This represents an actionable bug or a defensive code improvement
The suggested fix (checking
if timeout > 0before applyingWithTimeout, otherwise usingWithCancel) 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 wellThis YAML lines up cleanly with the new config types: top-level
AdapterConfig,spec.hyperfleetApi,params(including env and event sources), structuredpreconditions(API call + extracts + conditions + CEL expression), richresources(inline andmanifest.ref), andpostwithbuild/conditions/data/observed_generation/lastUpdatedplus apostActionsAPI 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 surfaceThe 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 likeBuildRefContentandManifestItemsare correctly excluded from YAML viayaml:"-", which keeps the on-disk schema clean while giving the loader room to enrich configs.No structural issues stand out in this type layer.
1680106 to
dc4a921
Compare
There was a problem hiding this 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
\nwould not be properly escaped.Consider using Go's
strconv.Quotewhich 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), nilDon'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.CleanupTimeoutunless 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
CleanupLeakedContainersfunction useslistCmd.Output()(line 277), which only captures stdout. Errors written to stderr are silently ignored. While this is acceptable for best-effort cleanup, usingCombinedOutput()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
setupSuccessflag adds a second cleanup path in addition to thet.Cleanupalready registered byStartContainer. While this is safe due to the idempotent nature ofTerminate, it adds complexity.The benefit is that this deferred cleanup runs when the function returns (even on panic), while
t.Cleanupruns 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 thet.Cleanupmechanism.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 failuresAlso applies to: 186-186
configs/templates/job.yaml (1)
11-23: Job template looks good; consider TTL for completed JobsSpec and labels are consistent. To avoid orphaned Job/Pod objects piling up, you may want to set
ttlSecondsAfterFinishedon the Job spec as an optional hardening step.spec: - backoffLimit: 3 + backoffLimit: 3 + ttlSecondsAfterFinished: 3600cmd/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: Redundantfilepath.Cleancall.The comment correctly notes that
filepath.Joinalready callsCleaninternally, making the explicitCleancall redundant. While defensive, this could be simplified to justreturn 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
⛔ Files ignored due to path filters (1)
go.sumis 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
Cleanupmethod 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 consistentField 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 soundSelectors, 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-designedThe 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 surfaceThis 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 solidThe 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
createAPIClientkeeps 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/v3is a valid and legitimate Go module.The original comment incorrectly identifies
go.yaml.in/yaml/v3as a typo. Web search confirms this is the new official module path maintained by the YAML organization (github.com/yaml/go-yaml), whereasgopkg.in/yaml.v3is 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_IDtoBROKER_SUBSCRIPTION_IDandTOPICtoBROKER_TOPICmakes the configuration more explicit and aligns with the test expectations intest/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.,
BuildRefvsBuildRefContent), 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.
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... make[1]: Entering directory '/home/xueli/hyperfleet-adapter' 📦 Building image: localhost/hyperfleet-integration-test:latest [+] Building 132.7s (14/14) FINISHED docker:default ✅ Integration test image built successfully! 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: === RUN TestAdapterEnvironmentVariable ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
dc4a921 to
c8c7587
Compare
There was a problem hiding this 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
formatCELValuefunction only escapes double quotes in strings (line 255), missing backslashes, newlines, tabs, and other control characters. This creates inconsistency with line 300, which correctly usesstrconv.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 ifTerminateor 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.Cleanupregistration 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 guaranteesk3sContaineris 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
Conditiontype here usesstringforOperator, whileinternal/criteria/evaluator.godefinesConditionDefwithOperator stringas well, butinternal/criteria/types.godefines a typedOperatoralias. Consider aligning these types or adding a note about the conversion path.Based on the relevant code snippets,
internal/criteria/types.godefinesOperator stringas a type alias. The conversion happens inEvaluateConditionsWithResultwhereOperator(cond.Operator)is used. This works but could be more explicit.
46-61: Consider documenting the mutual exclusivity of Parameter configuration options.The
Parameterstruct 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 anotherreturn falseon 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 newCELEvaluatorinstance. For repeated evaluations with the same context, this could be inefficient. Consider caching the CEL evaluator in theEvaluatorstruct or documenting this as a known trade-off.
270-275: ConditionDef defined late in file - consider moving to types.go.
ConditionDefis a public type that should arguably live intypes.goalongside 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
getProjectRootfunction assumes a fixed directory structure. If the file moves, this breaks. Consider usinggo.moddetection 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
okreturn value fromruntime.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
_ = adapterConfigand_ = apiClientlines 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
adapterConfigandapiClient?internal/config_loader/validator.go (4)
18-50: Validation error aggregation is solid; consider adjusting empty-error behaviorThe aggregation pattern looks good and produces readable, path-scoped messages. One minor nit:
ValidationErrors.Error()returning"no validation errors"whenlen(ve.Errors) == 0can 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
*ValidationErrorsis never used as anerrorwhen empty (documenting that invariant).This is non-blocking but worth aligning before broader use.
56-96: MakeValidatoreither single-use by contract or reset its state on eachValidatecall
Validator.Validate()never clearsv.errors, so if a singleValidatorinstance is reused, errors from a previous run will be retained and causeHasErrors()to stay true even if the subsequent config is clean. That’s easy to hit in tests or long-lived components that cache aValidator.Two options:
- Treat
Validatoras single-use and document that clearly, or- 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
definedParamsandcelEnv, so resettingerrorsis the main missing piece.)
281-317:initCELEnvcorrectly derives variables; watch interaction withresourcesif it ever appears indefinedParamsBuilding the CEL environment from
definedParamswithDynTypeis a pragmatic choice for validation, and theaddedRootsmap prevents redundant variable declarations.One subtle edge case: if
GetDefinedVariables()ever returns keys likeresources.foo, the loop will register aresourcesvariable withDynType, 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
resourceswon’t have the intendedmap<string, dyn>type. Ifresources.*is meant to be purely reserved (never indefinedParams), this is fine; if not, you may want to either (a) excluderesources.*fromdefinedParams, 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 handlingThe manifest validation provides a useful semantic layer on top of schema validation, especially enforcing
apiVersion,kind, andmetadata.name.Two potential refinements:
Non-map manifests
Right now, ifresource.Manifestisn’t amap[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.
ref/refstype assumptions
refis asserted asstringand non-empty, but a non-string value will just bypass this branch instead of producing a clear error.refsis only checked for presence, not shape.If the schema validator doesn’t already guarantee types here, you might want to validate
refis a non-empty string andrefshas 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
⛔ Files ignored due to path filters (1)
go.sumis 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
setupSuccessflag ensures containers are terminated immediately if setup fails, preventing resource leaks during test failures. This complements thet.Cleanupalready registered byStartContainer.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
imagePatternmust 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
imageTagparameter 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 toSUBSCRIPTION_IDandTOPICimproves 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
APIErrortype provides comprehensive HTTP error context with proper error wrapping support. The implementation includes:
- Rich error details (method, URL, status, body, timing, attempts)
- Proper
Unwrap()forerrors.Is/Ascompatibility- 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
AdapterConfigtype follows Kubernetes-style API conventions withapiVersion,kind,metadata, andspecfields. 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
ManifestRefis used elsewhere in the codebase. Manual verification is required by inspecting related files ininternal/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
Evaluatortype andNewEvaluatorconstructor follow good patterns with nil-safety for the context parameter.
388-418: LGTM! Comprehensive numeric type handling.The
toFloat64function 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 ofgetNestedField. However, based on the information provided in your review comment, I can still analyze the situation:The review comment states that:
getNestedFieldis defined at lines 420-423 ininternal/criteria/evaluator.go- The code uses
e.context.GetNestedField(field)instead- 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:
- The results of searching for all occurrences of
getNestedFieldin the codebase (case-sensitive)?- Is
getNestedFieldexported (capitalized) or unexported (lowercase)?- 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. Usingnginx:latestis 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 theimageTagparameter has a default value. Let me search the configuration files for this parameter.
<function_calls>
#!/bin/bashSearch 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
imageTagparameter has a sensible default value defined.internal/config_loader/loader_e2e_test.go (2)
20-101: LGTM! Comprehensive E2E test coverage.
TestLoadTemplateConfigthoroughly 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
findExtractByAshelper 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
createAPIClientfunction 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.Int32for 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
APIErrorconstruction 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.HasPrefixto 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 registryThe 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 variablesThe 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 viaGetDefinedVariables(), 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; verifyparam.Build.Conditionsvalue types can’t be nilThe CEL validation walks all the main expression sites (preconditions, post-action
when, andpost.params[*].build.{conditions,data}), which is great. One thing to explicitly verify is the shape ofparam.Build.Conditionsvalues: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, orMessageare 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 sensibleParsing 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 ofDynTypeand dynamic variables.No changes needed here.
463-468: PublicValidatewrapper keeps the API simple and backward compatibleThe top-level
Validate(*AdapterConfig)delegating toNewValidator(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.
c8c7587 to
53abfb6
Compare
There was a problem hiding this 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
existsoperator generateshas(field)for nested paths (line 234). WhileOptionalTypes()is enabled (line 59), the prior review flagged thathas(a.b.c)can error if intermediate selectors likea.bare null.Verify that the CEL environment's
OptionalTypes()configuration adequately handles nestedhas()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 usingstrconv.Quotefor consistent string escaping.The string case only escapes double quotes, while the
reflect.Stringcase at line 297 usesstrconv.Quotefor complete escaping. For consistency and correctness, usestrconv.Quotehere 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.
CleanupLeakedContainersreturns 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
k3sContaineris 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 theimageTagparameter instead of hardcoding.The image tag is hardcoded to
v1.0.0, but the adapter config defines animageTagparameter (with defaultv1.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 variablershadows the receiver.The loop variable
rshadows the method receiverr *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
RetryBackofffield 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
Postfield uses a pointer (*PostConfig) to indicate it's optional, but other optional slice fields likeParams,Preconditions, andResourcesdon'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
Parameterstruct 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
SourcevsBuildvsBuildRefvsFetchExternalResource- 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
Operatorfield 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
⛔ Files ignored due to path filters (1)
go.sumis 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
setupSuccessflag 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
forceCleanupContainerwhenTerminate()fails adds resilience.
178-203: Improved cleanup robustness with fallback.Capturing
containerIDearly 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
imageTagparameter 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 inconfigs/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 injectAuthorization(Bearer token) andContent-Type: application/jsonheaders 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:
- The API client automatically sets these headers for all requests
- Whether explicit header configuration is required for the
reportClusterStatusaction- 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
ParseTimeoutmethod 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
UnmarshalManifestmethod has clear semantics:nil, nilfor nil/empty input,map, nilfor valid map, andnil, errorfor 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, andexists(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.Durationas 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
DefaultClientConfigconstructor properly initializes all fields with sensible defaults and creates an empty map forDefaultHeadersto avoid nil map writes.
69-85: LGTM!Using pointer types for
RetryAttemptsandRetryBackoffallows 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
IsRetryablelogic 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:
- Examining the actual
configs/broker-configmap-pubsub-template.yamlfile to confirm the changes- Searching for references to the old key names (
SUBSCRIPTION_ID,TOPIC) throughout the codebase- Identifying any consumers (Go code, deployments, etc.) that may still reference the old key names
- 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 newBROKER_SUBSCRIPTION_IDandBROKER_TOPICnames. 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
--configflag 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()forerrors.Is/Assupport- 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
Evaluatemethod properly:
- Handles empty expressions gracefully
- Performs parsing and type-checking with fallback to parsed AST
- Logs type check issues at appropriate verbosity level
- Determines
Matchedstatus 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
Domethod 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
APIErrorwith 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
maxDelayto 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
Domethod, 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
externalTemplateresource 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-adaptercould 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:
- The hyperfleet-adapter's implementation handles
value: truewith "exists" correctly (and the value is used or ignored)- This is a redundant field that's accepted but unused
- 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"withvalue: 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.Manifestasinterface{}appropriately handles various manifest formats (inline maps, file references, arrays)ManifestItemswithyaml:"-"correctly hides internal loader state from serializationDiscoveryConfigprovides flexible resource discovery optionsWhenExprsupports both CEL expressions and structured conditionsThe type definitions align well with the test configuration file structure.
53abfb6 to
6174f0e
Compare
There was a problem hiding this 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.Quotefor 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), nilThis 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
requireassertions fail. However, the nil check at line 100 is redundant since the cleanup block is only entered whenk3sContainer != 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, andimageTagare 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().
GetDefinedVariablescurrently adds entries even whenExtractField.AsorResource.Nameare 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, andUnmarshalManifestall requirer.Manifestto bemap[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 tostring) before these helpers run, or extend these functions to handle that case. If loader already guaranteesmap[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.
ParameterBuildusesobserved_generationforObservedGenerationandlastUpdatedforLastUpdated. 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_schemaexpect 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.Callerand 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 underinternal/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.
createAPIClientreimplements timeout parsing instead of usingHyperfleetAPIConfig.ParseTimeout(), which is already defined inconfig_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
EvaluationContextacross parameterized subtests works correctly since subtests run sequentially. However, ift.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
httpClientstruct: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 ofrand.Float64()incalculateBackoff.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis 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.Cleanupregistered at line 99 and this explicitCleanup()method both terminate the same container. When tests calldefer env.Cleanup(t), the container may be terminated twice. This is safe sinceTerminate()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
setupSuccessflag pattern ensures the container is terminated immediately if setup fails (before assertions), while the existingt.CleanupfromStartContainerhandles normal test completion. SinceTerminate()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
forceCleanupContaineris a sensible safety net.
177-204: Well-designed cleanup with captured container identity and fallback.Capturing
containerIDandcontainerNameoutside 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
CleanupLeakedContainersfunction correctly uses theancestorfilter 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_IDandBROKER_TOPICis 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
clusterIdand use of.metadata.namefor themanaged-bylabel 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.goand 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. Thegopkg.in/yaml.v3 v3.0.1version 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.Int32for 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
IsAPIErrorworks with both directAPIErrorinstances and wrapped errors usingfmt.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
InandExists.test/integration/config-loader/config_criteria_integration_test.go (4)
1-14: Proper use of build tag for integration tests.The
//go:build integrationtag 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 usesstrings.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
toFloat64tests 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
HasFieldreturning 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 abouthas()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 makehas()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), nestedhas()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:
- Use optional selection syntax if available in the CEL evaluator, or
- 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
Validatefunction 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.
6174f0e to
4f52202
Compare
There was a problem hiding this 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: NormalizeretryAttemptsso at least one HTTP attempt is madeIf
c.config.RetryAttemptsor*req.RetryAttemptsis set to 0 or a negative value, the loopfor attempt := 1; attempt <= retryAttempts; attempt++never executes, so no HTTP request is sent and you only get an APIError withAttemptsequal 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: Alignexistsexpectations with safer CEL expressions once ConditionToCEL is fixedThe
TestConditionToCELcases for theexistsoperator currently asserthas(...)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 likemetadata.?name != nullorcluster.?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:exists→has(fieldPath)remains unsafe for nested fields; prefer optional-safe selectionThe
existsbranch still emitshas(fieldPath)for nested paths (e.g.cluster.status.phase→has(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), nilPer CEL semantics,
has(a.b.c)still requires evaluating intermediate selectors; ifaora.bis absent/null, evaluation can fail rather than safely returningfalse. 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), nilIf you apply this, the
exists-related expectations incel_evaluator_test.goshould 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 betweenBROKER_TOPICandBROKER_GOOGLEPUBSUB_TOPIC.The template introduces
BROKER_TOPIC(adapter-specific) while the broker library config referencestopicin thebroker.yamlsection, and the README documentsBROKER_GOOGLEPUBSUB_TOPICas 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.Cleanupregistration prevents container leaks if subsequent assertions fail. However, note that:
- The inner nil check on line 100 is redundant since line 98 already guards entry.
- Tests using this setup typically call
e.Cleanup(t)explicitly (lines 159-171), which will attemptTerminate()again. This is safe (testcontainers handles it gracefully) but may produce warning logs.Consider setting
e.Container = nilin the explicitCleanupmethod 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
_ = adapterConfigand_ = apiClientassignments (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 nestedresources.<name>usages
GetDefinedVariablesregisters resource aliases as"resources."+r.Name, e.g.resources.clusterNamespace. HoweverisVariableDefinedonly 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 indefinedParams.As a result, this valid usage is reported as “undefined template variable”, even though the alias
resources.clusterNamespaceis 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 explicitIsFieldNotFoundcheck 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 theOperatortype for consistency.
ConditionDef.OperatorusesstringwhileEvaluationResult.Operatoruses theOperatortype. While the string-based approach offers flexibility for deserialization, using the typedOperatorwould provide compile-time safety.If YAML/JSON unmarshaling is a concern, consider adding custom unmarshaling logic to maintain type safety.
310-334:evaluateContainsdoes not support map types.The
containsoperator 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
⛔ Files ignored due to path filters (1)
go.sumis 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.yamlsection.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) andgo.yaml.in/yaml/v3 v3.0.4(line 118, indirect). These are legitimate but distinct module paths:gopkg.in/yaml.v3is the legacy import path, whilego.yaml.in/yaml/v3is 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/v3as 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/exprandantlr4-go/antlr/v4are mutually compatible by design. Specifically,cel.dev/expr v0.24.0explicitly depends ongithub.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
publishAndWaitForMessagehelper properly handles the race condition where the subscriber may not be ready immediately afterSubscribe()returns. The implementation is clean with proper resource cleanup viadefer 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
forceCleanupContainerwhenTerminatefails is a nice touch.Minor: Line 160 shadows the outer
ctxvariable 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
CleanupLeakedContainersfunction provides a convenient way to clean up stray containers from previous failed test runs. Theancestor=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
returnon 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
SchemaValidatorstruct properly encapsulates validation state and provides a clear API withValidateStructure(),ValidateFileReferences(), andLoadFileReferences().
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
byNameorbySelectorsto be configured.
464-498: Legacy wrappers create overhead but maintain compatibility.Each legacy function creates a new
SchemaValidatorinstance. 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
createAPIClientfunction 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 == 0after parsing (which could happen with an empty string), it silently uses the client's default. This behavior seems intentional based on theif timeout > 0check.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 combinedEvaluateConditionspaths.
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
Containsoperator 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, andGetPreconditionByNameverify 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 thetemplates/deployment.yamlfile or how tests handle it. This is a situation where manual verification is necessary.Verify that the external template file
templates/deployment.yamlexists relative to the test configuration, or that the test framework handles the reference appropriately.The
externalTemplateresource intest/testdata/adapter_config_valid.yaml(line 128) referencesref: "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 solidThe operator definitions, validation helpers, evaluation context, and
EvaluationErrorstruct 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 semanticsThe 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-structuredThe
BackoffStrategy,ClientConfigdefaults, request option helpers, response helpers, andClientinterface are consistent and give a clear, extensible surface for the HTTP client. They line up cleanly with the implementation inclient.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 usageThe 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
-1as a sentinel forFailedConditionis 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
NotEqualsandNotInis 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
getNestedFieldis truly unused or howEvaluationContext.GetNestedFieldis implemented. Without access to the codebase, I cannot:
- Search for all calls to
getNestedFieldacross files- Examine the
EvaluationContext.GetNestedFieldimplementation to see if it delegates to the standalone function- Confirm the function's actual usage patterns
Rewritten Review Comment:
Verify if
getNestedFieldfunction is actually unused or if it's called byEvaluationContext.GetNestedField.The review comment claims this standalone function is unused because
Evaluatorusese.context.GetNestedField()instead. However,EvaluationContext.GetNestedFieldmay 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 aCanInterface()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(), nilLikely 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.Numberis only relevant whenjson.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 — ifUseNumber()is enabled, thenjson.Numbervalues would indeed causetoFloat64to fail. However, without access to the actual codebase, I cannot confirm whether this scenario actually occurs.
toFloat64does not handlejson.Numbertype.When unmarshaling JSON into
map[string]interface{}withjson.Decoder.UseNumber(), numeric values are represented asjson.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)
4f52202 to
da704cf
Compare
There was a problem hiding this 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: Theexistsoperator for nested fields remains unsafe despite the comment.The comment claims
cel.OptionalTypes()makeshas()safely handle null intermediates, but this is incorrect per CEL specification.has(a.b.c)will error ifa.bis null — it does not return false.For nested paths like
cluster.status.phase, the current outputhas(cluster.status.phase)will fail at runtime if any intermediate is null.Consider one of these fixes:
- Chain null checks:
has(cluster) && has(cluster.status) && has(cluster.status.phase)- Use optional-safe selection (if supported):
cluster.?status.?phase != nullcase "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.Quotewhich 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 forevaluateLessThan.
TestEvaluateGreaterThanincludes a test for non-numeric field (line 305-309), butTestEvaluateLessThanis 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
existstests 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
ctxwithin the subtest. While this works correctly because subtests run sequentially withoutt.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 inMergeto prevent panic.If
otheris nil, iterating overother.Datawill 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
⛔ Files ignored due to path filters (1)
go.sumis 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: NewimageTagparameter looks good.The parameter follows the established pattern with appropriate defaults and is correctly marked as optional.
384-388: Verify: Headers removed fromreportClusterStatusAPI call.The
reportClusterStatusaction no longer specifiesAuthorizationorContent-Typeheaders. 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-Typecausing body parsing issuesIf 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
setupSuccessflag 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_IDkey, 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 aBROKER_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.0is 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!
ClientConfigstruct is well-documented.DefaultClientConfig()correctly initializes theDefaultHeadersmap to avoid nil map panics when adding headers.
69-149: LGTM!The
Requeststruct correctly uses pointer types for optional per-request overrides, enabling distinction between "not set" and explicit values. AllRequestOptionfunctions properly handle nil map initialization before adding headers.
155-207: LGTM!
Responsehelper methods correctly categorize HTTP status codes.IsRetryableappropriately includes timeout (408), rate-limiting (429), and gateway errors (502-504) alongside general server errors.BodyStringsafely handles nil body.
213-232: LGTM!The
Clientinterface 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!
HyperfleetAPIConfiguses string types forTimeoutandRetryBackoff, which simplifies YAML parsing. The accessor methods (likeParseTimeout()) handle conversion to proper Go types.
46-71: LGTM!
Parameterstruct appropriately usesinterface{}for the flexibleBuildfield andyaml:"-"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. TheCondition.Operatorfield uses the samestringtype asinternal/criteria/types.go, ensuring consistency.
113-134: LGTM!
Resourceusesinterface{}for flexible manifest definitions andyaml:"-"for loader-populated arrays.DiscoveryConfigcovers 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.ManifestRefcleanly 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!
createAPIClientcorrectly 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
TestNewClientWithOptionsis 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.AddInt32ensures 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
calculateBackoffmethod 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!
APIErrortests thoroughly verify error string formatting, status helpers, and error extraction viaerrors.IsAPIError. The wrapped error test ensures proper error chain traversal.
598-642: LGTM!
TestAPIErrorInRetryExhaustedprovides excellent end-to-end validation thatAPIErrorcorrectly 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
wantErrorfield provides extensibility for future error cases.
172-217: LGTM!Good coverage of the
inoperator including the error case when the value is not a list.
219-270: LGTM!Thorough testing of the
containsoperator for both string substrings and array elements, including error handling for invalid types.
428-516: LGTM!Comprehensive test coverage of
EvaluateConditionacross 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
getNestedFieldandtoFloat64utilities with comprehensive type handling.
667-740: LGTM!Error handling tests and safe getter tests are well-structured with appropriate assertions.
742-873: LGTM!
EvaluateConditionWithResultandExtractFieldstests 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
IsFieldNotFounderror 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
containsoperator on slices for tracking created resources.
189-248: LGTM!Excellent coverage of the Kubernetes labels containment pattern, testing both
map[string]interface{}and typedmap[string]stringscenarios.
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 availablegreaterThanandlessThanoperators.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.HasPrefixto 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
resolvePathfunction now correctly prevents path traversal attacks by:
- Converting paths to absolute form
- Using
filepath.Relto compute the relative path from base to target- 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.Randin the struct. This has been properly addressed by removing therandfield and using the package-levelrand.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
Domethod.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
IsValidOperatorfunction 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
GetDefinedVariablesmethod 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 := rangepattern 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, nilfor 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) andrefs(array) patterns, andnormalizeToStringKeyMapproperly handles themap[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{}). Thefmt.Sprintffallback 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
existsoperator 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
NewCELEvaluatorcorrectly 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!
ConditionsToCELcorrectly 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. SinceParsealways initializescfg := &loaderConfig{}before applying options, nil checks are unnecessary here.
84-130: LGTM! Well-designed loading API with sensible defaults.The
Loadfunction automatically derivesbaseDirfrom the config file path but prepends it so user-providedWithBaseDiroptions can override. The validation pipeline is correctly invoked after unmarshaling.
151-197: LGTM! Well-structured validation pipeline.The pipeline correctly:
- Runs core structural validators first
- Validates adapter version if specified
- Validates file references before loading them (correct order)
- 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.Oncepattern 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 fieldsGetFieldOrDefault: returns default on error/nilGetFieldSafe: silently returns nil (null-safe)HasField: boolean check for existence
110-163: LGTM! Comprehensive condition evaluation with operator support.The
EvaluateConditionWithResultmethod correctly handles all operators including the negation fornotEqualsandnotIn. 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
FailedConditionindex 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 useserrors.Asfor error unwrapping.The implementation now properly detects
FieldNotFoundErroreven when wrapped inside other error types likeEvaluationError, 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.
da704cf to
182589c
Compare
There was a problem hiding this 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
retryAttemptsis 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
existsoperator at line 311 generateshas(a.b.c)for nested fields, which will error at runtime if intermediate fields (likea.b) are null. The CELhas()macro requires evaluating the selector chain, so ifa.bdoesn't exist, accessinga.b.cthrows an error beforehas()can check for presence.For null-safe nested field checking, the expression should either:
- Chain parent checks:
has(a) && has(a.b) && has(a.b.c)- Use optional selection (if supported):
a.?b.?c != nullThe 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.Cleanupimmediately 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 checkk3sContainer != nilon 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, butTerminate()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.namespaceor 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.
validateFileExistscorrectly checks emptiness, existence, and that the target is a file.resolvePathnow usesfilepath.Abs+filepath.Relto ensure targets stay underbaseDir, including for absoluterefPath; this addresses the prior path‑traversal concern nicely.If you want slightly clearer diagnostics, you could include the underlying
filepath.Relerror 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.eventGenerationIdis (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.Loadalready setsbaseDirfromconfigPath, so passingWithBaseDir(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 caseThese subtests clearly pin down
EvaluateSafebehavior 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 whatValueshould be on error (e.g., explicitlynil) to fully lock in the error-contract.
220-236: EvaluateBool happy paths covered; optional: add non-bool expression caseThe 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
EvaluateBoolfails appropriately (or document that it coerces).
238-255: EvaluateString tests look good; optional: add wrong-type assertionString 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 thatEvaluateStringreturns an error rather than silently coercing.
465-497: formatCELValue test matrix is solid; optional: extend slice coverageThe 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[]intor 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
⛔ Files ignored due to path filters (1)
go.sumis 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
publishAndWaitForMessagefunction:
- 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
CleanupLeakedContainersfunction provides a helpful way to clean up containers from previous failed test runs. Theancestorfilter 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
setupSuccessflag with deferred cleanup ensures the container is terminated if any assertions or setup steps fail after the container starts. This complements thet.Cleanupregistered byStartContainer:
t.Cleanupruns at test end (success or failure)- This deferred cleanup runs immediately on setup failure within
SetupTestEnvPrebuiltSince
Terminateis 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
SchemaValidatorand exposingValidateStructure,ValidateFileReferences, andLoadFileReferencesis 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 toSchemaValidatorkeep 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
clusterStatusPayloadand 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_TOPICand 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.
TestLoadValidTestConfigprovides a simple sanity check that the test config parses, andfindExtractByAscorrectly 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
--configwith a fallback toADAPTER_CONFIG_PATHand feeding that intoconfig_loader.Loadis straightforward. PassingWithAdapterVersion(version)is good for catching mismatches, but note that with the defaultversion = "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 setversionconsistently 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.createAPIClientcleanly convertsHyperfleetAPIConfiginto client options: it usesParseTimeout, respects zero/empty values, and validatesRetryBackoffagainst the supported strategies before wiring it into the client.- Capturing
adapterConfigandapiClientin 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. UsingminNodes-1/maxNodes+1to 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.Randhas 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.Quotewhich 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.v3and 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 creationThe 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, andExpression. This gives a clear contract forEvaluate.
115-138: Nested field access tests are concise and effectiveThe 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 operatorsThis table tests string/number comparisons, list
in/notIn,contains, multipleexistsvariants (including nested), invalid operators, and nested-field operators, giving strong confidence inConditionToCEL. The comments clarifying that nested fields are not wrapped for null safety and are handled byEvaluateSafeare helpful.
395-411: ConditionsToCEL combination behavior is clearly validatedYou 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 helpersThis test ties together
EvaluateCEL,EvaluateCELBool,EvaluateConditionAsCEL, andEvaluateConditionsAsCELagainst 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 casesThe assertions on the exact expressions for one condition and for multiple conditions (
GetCELExpressionForConditions) nicely complement the lower-levelConditionToCELtests.
499-572: EvaluateSafe error-handling matrix is clear and non-redundantThis table-driven test nicely complements
TestCELEvaluatorEvaluateSafeby focusing on nested-path success/error patterns andhas()behavior (existing, missing leaf, missing intermediate). The success vs. error expectations and substring checks onErrorReasongive a precise contract for callers relying on structured error handling.
182589c to
d98944f
Compare
There was a problem hiding this 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 assumptionsThe new cleanup behavior looks good (attempt-level termination, fallback
forceCleanupContainer, and longer timeouts), but:
Line 39 still documents
CleanupTimeoutdefaulting 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/CleanupLeakedContainersassumedockerorpodmanCLIs 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: NormalizeretryAttemptsto avoid zero-attempt requests and nillastErrIf
c.config.RetryAttemptsor*req.RetryAttemptsis 0 or negative, thefor attempt := 1; attempt <= retryAttemptsloop never runs. In that case:
- No HTTP request is ever sent.
lastErrandlastRespremain nil.- The function returns an
APIErrorwithAttempts=0andErr=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 -C2internal/config_loader/validator_schema.go (1)
410-440: Path traversal protection inresolvePathlooks solidThe new
resolvePathusesfilepath.Abs+filepath.Reland rejects any resolved path whose relative form starts with"..", which closes the earlier traversal gap forbuildRef/manifest.ref(s)while still allowing absolute paths insidebaseDir.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 optionsThe docs introduce
WithJSONBodyand backoff configuration, but the examples slightly under-use them:
In the “Request Options” block you mention
WithJSONBody, but the POST example usesclient.Post(ctx, url, body)without showing how to attach the JSON header viaWithJSONBody. Consider either:
- Demonstrating
client.Post(ctx, url, nil, hyperfleet_api.WithJSONBody(body)), or- Explicitly stating that
Postsets the appropriate headers if that’s the case in the implementation.In the
createAPIClientexample,RetryBackoffis cast directly toBackoffStrategywith no validation; if the actualcreateAPIClientincmd/adapter/main.goperforms 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.Operatoris a dedicatedOperatortype; 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
ClientConfigdefaults plusResponse.IsRetryablebehavior 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, 504inIsRetryableis redundant with theIsServerError()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 supportedThe original review comment raises concerns about
go.yaml.in/yaml/v2andgo.yaml.in/yaml/v3being "atypical" or potentially mis-typed variants ofgopkg.in/yaml.vX. However, verification confirms thatgo.yaml.inis 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 tidyon 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 inAPIError.Error()If
Erris 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: AlignSupportedOperatorscomment with its actual formThe 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 tospec.params.buildand field selectors
validateTemplateVariablescurrently walks:
- Precondition API call URLs/bodies/headers.
- Resource manifests and discovery
namespace/byNameand label selector values.- Post action API calls.
spec.post.params[*].buildmaps.Two small gaps:
spec.params[*].build: Parameters at the top level also supportBuildwith templated structures, but those aren’t currently scanned, so undefined template variables in those builds will only surface at runtime.
FieldSelectorin discovery: OnlyLabelSelectorvalues are validated; templatedFieldSelectorvalues (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 viamanifest.refs(ManifestItems)
validateK8sManifestscurrently inspects onlyresource.Manifest:
- It skips entries with
manifest.ref/manifest.refs, expecting those to be handled elsewhere.- After
loadFileReferences, singlerefresources get theirManifestreplaced with the loaded manifest (and are validated), but multi-refsresources store loaded manifests inresource.ManifestItemswhileManifeststill only holds the refs list.As a result, inline manifests and single-ref manifests are structurally validated, but resources that use multiple
manifest.refswon’t have their loadedManifestItemsvalidated as Kubernetes manifests.To close that gap, consider iterating over
resource.ManifestItemsas 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()), causingctx.Done()to close immediately andctx.Err()to returncontext.DeadlineExceededhttp.Client.Timeout == 0means "no timeout" — the request can wait indefinitelyThis 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 == 0produces immediately-expired context, not "no timeout"
doRequestunconditionally wraps the parent context withcontext.WithTimeout(ctx, timeout). Whentimeout == 0, this creates a context with deadline =time.Now()(immediately expired), causing requests to fail withcontext.DeadlineExceeded. This differs from thehttp.Clientconvention whereTimeout == 0means "no timeout" (indefinite wait).To align with standard library semantics, consider:
- Skipping
WithTimeoutwhentimeout <= 0, or- Treating
0as "use config default" and disallowing the "no timeout" case explicitlyExample:
- // 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
⛔ Files ignored due to path filters (1)
go.sumis 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 windowThe longer context timeout plus
publishAndWaitForMessagewith a ticker-based retry loop is a good way to avoid brittle sleeps while keeping the test bounded byctx.Done(). Channel usage and cleanup (ticker stop, deferredcancel, andsubscriber.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 layeredThe split between
EvaluateSafe(always returns aCELResultwith categorized errors) andEvaluate(returning an error when evaluation fails) is clean and makes it straightforward for callers to choose strict vs. tolerant semantics. TheMatchedhandling for non-bool outputs viaisEmptyValueis 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 callhas(a.b.c), ifaora.bis null or missing, CEL will raise an evaluation error—not return false. Thehas()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 ofEvaluator.EvaluateCELandEvaluateConditionAsCEL.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
existsexpressions usinghas()will raise evaluation errors for missing parent paths—not return falseFor the
existsoperator with dotted paths (e.g.,has(cluster.status.phase)),ConditionToCELgenerates ahas()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
existschecks must use theEvaluateSafe()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 throughEvaluateSafe()and handle errors appropriately.internal/criteria/evaluator.go (2)
111-163: Condition evaluation helpers are coherent; equality vs numeric ops are intentionally distinct
EvaluateConditionWithResultplus 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 throughcompareNumbers/toFloat64, giving you tolerant numeric handling without conflating them with generic equality.evaluateExistsimplements 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:IsFieldNotFoundnow correctly unwraps nested errorsSwitching
IsFieldNotFoundto useerrors.Asagainst*FieldNotFoundErrormeans callers can reliably detect field-missing cases even when they’re wrapped in higher-level errors likeEvaluationError.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
Terminatewhich is idempotent is the correct approach since it may be called both here and int.Cleanup.
135-147: Robust setup failure handling.The deferred cleanup block with
setupSuccessflag 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:
- Loads config with adapter version annotation
- Validates API base URL is configured
- 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
createAPIClientfunction 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:
- Examining the actual code at lines 156-159 in
cmd/adapter/main.goto see the complete context and any TODO comments- Searching for related TODO comments in the
cmd/adapter/directory as suggested in the review- 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 stateinternal/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.HasPrefixproperly 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, andUnmarshalManifesthandlenilresources, non-map manifests, and bothmap[string]interface{}/map[interface{}]interface{}vianormalizeToStringKeyMap. 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 ofbaseDirinto the loader options flowDeriving
baseDirfrom the config file directory inLoadand prependingWithBaseDir(baseDir)before user-supplied options ensures:
- Relative
buildRef/manifest.refpaths work out-of-the-box for file-based configs.- Callers can still override
baseDirexplicitly if needed.This keeps
Parsebyte-oriented andLoadpath-aware without special cases downstream.
148-198: Validation pipeline ordering is reasonable and extensibleThe sequence in
runValidationPipeline:
- Core structural schema checks.
- Optional adapter version check.
- File-reference validation/loading (gated on
baseDir).- 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 coverageThe suite exercises:
- Simple and compound expressions (bools, numerics, membership).
- Nested data access and
has()semantics viaEvaluateSafe.- Translation helpers (
ConditionToCEL,ConditionsToCEL) includingexistsand nested fields.- Integration with
Evaluatorand edge cases informatCELValue.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/bashSearch for BaseDelay and MaxDelay definitions
rg -n "BaseDelay|MaxDelay" --type go -B2 -A2
</function_calls>
#!/bin/bashFind the config struct and related definitions
rg -n "type.*Config" --type go -A10 | head -100
</function_calls>
#!/bin/bashLook 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
clusterStatusPayloadbuild 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
reportClusterStatusaction 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
omitemptyfor optional fields and pointer forPostConfigallows 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
TimeoutandRetryBackoffprovides flexibility for parsing duration and backoff strategies, whileRetryAttemptsappropriately usesintfor the retry count.
46-63: LGTM! Flexible Parameter type with clear documentation.The
Parametertype appropriately usesinterface{}for theBuildandDefaultfields to support flexible schema definitions, as documented in the comment. The pattern ofBuildRef+BuildRefContentcleanly separates the YAML reference from the runtime-loaded content. The use ofyaml:"-"forBuildRefContentcorrectly 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:
APICallincludes all necessary HTTP configurationExtractFieldsupports nested JSON path extractionConditionuses flexibleinterface{}forValueto accommodate different types (strings, lists, booleans)Preconditioncleanly supports both patterns via optional fieldsThe types align well with the test configuration usage.
113-134: LGTM! Flexible resource configuration types.The resource types effectively model different resource patterns:
interface{}forManifestsupports both inline and external referencesManifestItems(yaml:"-") follows the same loader-populated pattern asBuildRefContentDiscoveryConfigsupports 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
Parametertype for post-params promotes consistencyPostActioncleanly combines API calls with conditional executionWhenExprmirrorsPreconditionstructure, supporting both expression and condition-based patterns- Optional fields properly use
omitemptyand pointersThe types align with the post-processing configuration in the test data and complete a cohesive type system for the entire adapter configuration.
d98944f to
cfcabf5
Compare
There was a problem hiding this 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 argumentAll three integration tests now pass an explicit empty string for the new
fieldSelectorparameter, preserving previous behavior while matching the updated API. Consider adding at least one integration test that exercises a non-emptyfieldSelectorto 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 usageThe 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 simplekey=valuepairs, you may want to briefly call that out (or show onlykey=valueexamples, 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 wrappingThe extended
ListResourcescorrectly wiresfieldSelectorthrough logging, validation, and the client options, while preserving existing label selector behavior. One small polish opportunity:parseFieldSelectoralready returns aKubernetesErroron malformed input, and that error is then wrapped again byerrors.KubernetesError("invalid field selector %s: %v", fieldSelector, err). You could simplify by havingparseFieldSelectorreturn a plainerror(e.g., viafmt.Errorf) and letListResourcesbe the single place that converts it into aKubernetesError, avoiding nested ServiceError messages.
214-263: Field selector parsing is straightforward; consider trimming whitespace for robustnessThe
parseFieldSelector/splitSelectorPairs/splitKeyValuehelpers correctly handle simple comma‑separatedkey=valuepairs 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 withstrings.TrimSpaceinsplitKeyValuewithout 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
Cleanupmethod (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:AsAPIErroris redundant—consider removing or documenting the distinction.
AsAPIErrordirectly callsIsAPIErrorand provides no additional functionality. While the comment says it's "for more idiomatic Go usage," both names are commonly used (errors.Aspattern). 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:
t.Cleanup(registered here) will run at test endCleanup()method (line 166) is likely also called by testsWhile
Terminateis typically idempotent, consider settingk3sContainer = nilafter termination in theCleanupmethod, or document that callers should not callCleanup()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.Operatortocriteria.ConditionDef.Operator. Based on the relevant code snippets,criteria.Operatoris a typed string (type Operator string), whileconfig_loader.Condition.Operatoris 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 withexistsoperator.The
TestEvaluateExiststest covers empty string, nil, empty slice, and empty map, but doesn't test zero numeric value (e.g.,0or0.0). Given that0is returned astrue(line 457-459), you might want to add explicit documentation or a test case that clarifies this behavior, since some users might expect0to 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.Clientis provided viaWithHTTPClient, itsTimeoutmay conflict with the per-request context timeout created indoRequest(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: RedundantWithBaseDiroption.According to
internal/config_loader/loader.go, theLoadfunction automatically sets the base directory from the config file path. The explicitWithBaseDirhere is redundant since it passes the same directory thatLoadwould 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 handlingruntime.Callerfailure.The
okreturn value fromruntime.Caller(0)is discarded. If the call fails (unlikely but possible),filenamewill be empty, causinggetProjectRootto 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 extensibleRunning 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
coreValidatorsto accumulate into a sharedValidationErrors, but it’s fine as-is.internal/config_loader/validator.go (1)
150-225: Template variable validation covers key surfaces and uses a sensible heuristicWalking 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.,indexon 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 contextRight now, errors returned from helpers like
evaluateIn/evaluateContainsare propagated directly, so callers lose the association with the field/operator that failed, while field‑lookup failures are wrapped inEvaluationError. You can make error handling more consistent (and debugging easier) by wrapping evaluation errors inEvaluationErroras 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
⛔ Files ignored due to path filters (1)
go.sumis 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_TOPICisn'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
publishAndWaitForMessagehelper elegantly handles the race condition where the subscriber may not be fully ready afterSubscribe()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
APIErrorstruct captures all relevant information for debugging API failures, including retry attempts and duration. TheError()method provides clear, actionable error messages.
56-59: Consider whethercontext.Canceledshould be included in timeout detection.
IsTimeout()checks for HTTP 408 andcontext.DeadlineExceeded. Depending on usage patterns, you may also want to detectcontext.Canceledsince 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: Usingstrconv.Quotefor 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:existsoperator 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
existscase explaining thathas(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 likea.?b.?cfor null-safe field access. However, theexistsoperator (lines 307-314) generateshas(...)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.
EvaluateSafeprovides 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 theisEmptyValuefunction's handling of booleanfalsevalues. Let me start by examining the code context and control flow.
<function_calls>
#!/bin/bashFirst, let's understand the file structure and locate the relevant function
wc -l internal/criteria/cel_evaluator.go
</function_calls>
<function_calls>
#!/bin/bashRead 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/bashLet'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/bashGet the full isEmptyValue function
rg -n -A 50 "func.*isEmptyValue" --type=go internal/criteria/cel_evaluator.go
</function_calls>
<function_calls>
#!/bin/bashSearch 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/bashGet 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
imageTagparameter is well-defined with appropriate source, type, default value, and description. Marking it asrequired: falseis 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
requirefor setup assertions andassertfor 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
forceCleanupContainerwhen 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: Theancestorfilter IS supported by Podman and the code should work correctly.According to official Podman documentation,
podman pssupports 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.Randinstance 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
ClientOptionvariants 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
APIErrorwith 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 toadapterConfigandapiClientwith 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
createAPIClientfunction 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
RetryAttemptsis 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!
TestRealWorldScenarioprovides 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!
TestMapKeyContainmentprovides comprehensive coverage for theContainsoperator with bothmap[string]interface{}andmap[string]stringtypes. Good coverage of found/not-found scenarios.
284-363: LGTM!
TestNodeCountValidationuses 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!
TestLoadprovides comprehensive end-to-end loading test with a realistic YAML config. Good use oft.TempDir()for automatic cleanup.
80-174: LGTM!
TestParseuses table-driven tests effectively to cover valid configs and various validation error scenarios (missing apiVersion, kind, metadata.name, adapter.version).
491-523: LGTM!
TestGetRequiredParamsvalidates the filtering logic correctly, ensuring only required params are returned in order.
761-889: LGTM!
TestValidateFileReferenceshas 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!
TestLoadFileReferencesContentvalidates that file reference content is properly loaded and parsed. Good verification that the originalrefkey is replaced with actual content andBuildRefContentis populated correctly.
1068-1263: LGTM!
TestValidateResourceDiscoveryprovides comprehensive validation coverage for discovery configuration requirements including namespace, byName/bySelectors, and selector content validation.
1265-1335: LGTM!
TestLoadManifestRefsArrayvalidates the multi-manifest loading feature, verifying that both items are loaded intoManifestItemswith correct content.internal/config_loader/validator_schema.go (9)
16-29: LGTM!Clean
SchemaValidatorstruct design with config and baseDir fields. The constructorNewSchemaValidatoris straightforward.
31-51: LGTM!
ValidateStructureuses 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!
validateResourceDiscoveryandvalidateSelectorsenforce the required discovery configuration with clear error messages. Good validation that at least one selector type is defined when usingbySelectors.
252-297: LGTM!
validateFileReferencescollects all file reference errors before returning, providing a comprehensive error report. The error formatting with bullet points is user-friendly.
299-324: LGTM!
validateFileExistsproperly 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
resolvePathfunction now returns an error for paths that escape the base directory by usingfilepath.Reland checking for ".." prefix. This addresses the previous security concern.
391-408: LGTM!
loadYAMLFileproperly 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!
BuiltinVariablesprovides a clear list of always-available template/CEL variables. Good documentation.
31-79: LGTM!
GetDefinedVariablescomprehensively collects all variable sources. Good nil check at the start and proper handling of optional sections likePost.
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!
GetPostActionByNamecorrectly handles the additional nil check forc.Spec.Postbefore accessingPostActions.
175-182: Consider documenting the zero-value behavior.
ParseTimeoutreturns(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!
HasManifestRefandGetManifestRefscorrectly handle both singlerefand arrayrefspatterns. Good use of thenormalizeToStringKeyMaphelper for robustness.
252-274: LGTM!
normalizeToStringKeyMaphandles bothmap[string]interface{}(yaml.v3) andmap[interface{}]interface{}(yaml.v2) for robustness across YAML parser versions. Non-string keys are safely converted usingfmt.Sprintf.test/integration/config-loader/config_criteria_integration_test.go (8)
1-17: LGTM!Build tag
integrationis correctly specified. Imports are appropriate for integration testing with config_loader and criteria packages.
19-30: LGTM!
getConfigPathprovides flexible path resolution with environment variable override and fallback to runtime-based path resolution. Good for CI/local development flexibility.
32-113: LGTM!
TestConfigLoadAndCriteriaEvaluationprovides comprehensive end-to-end testing of config loading and precondition evaluation, including per-condition and combined evaluation with result verification.
115-170: LGTM!
TestConfigConditionsToCELvalidates the CEL conversion and evaluation pathway, ensuring conditions from config can be translated to CEL expressions and evaluated correctly.
172-238: LGTM!
TestConfigWithFailingPreconditionsprovides good coverage of failure scenarios with wrong phase, wrong provider, and missing vpcId. Tests correctly verify that conditions fail as expected.
266-359: LGTM!
TestConfigPostProcessingEvaluationsimulates realistic K8s resource data and validates nested field access, condition evaluation, and CEL expression evaluation against the simulated data.
361-417: LGTM!
TestConfigNullSafetyWithMissingResourcesprovides 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 andWithBaseDirprecedence look solid
loaderConfigplusLoaderOptionis used correctly, and prependingWithBaseDirwhile still allowing it to be overridden by later options gives callers flexibility without complicating the API. No changes needed here.
88-111:Load/Parseseparation keeps concerns cleanDeriving
baseDironce inLoadand feeding it intoParsevia 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 behaviorThe table in
TestCELEvaluatorEvaluateplus the nested-data andEvaluateSafetests cover happy paths, compound expressions, empty expressions, invalid syntax, and several error modes. This gives strong confidence in CELEvaluator’s behavior, especially around howMatched,Value, andErrorare populated.
257-393:ConditionToCELtest matrix nicely pins down operator semanticsThe cases here (including nested fields and the various
existsforms) clearly define the expected CEL syntax for each operator and path shape. This should make future refactors toConditionToCELmuch safer, since any accidental change in mapping will be caught.
465-497: Good edge‑case coverage inTestFormatCELValueVerifying quoting, escaping, numeric types (including
time.Duration), slices, and several unsupported kinds gives solid guardrails aroundformatCELValue’s behavior. This is a nice balance of realism and safety checks.
499-572: Nice focused scenarios forEvaluateSafeerror handlingThe dedicated
TestEvaluateSafeErrorHandlingsubtests 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 onIsSuccessvsHasError. 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
ValidationErrorplusValidationErrors.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 needsBuilding the CEL env from
definedParamsroots withcel.DynType(and always adding aresourcesmap) is a good way to keep validation flexible while still catching obvious parse errors. Skipping hard failures onCheckand 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 effectiveChecking for
apiVersion,kind,metadata, andmetadata.name(plus non‑emptyapiVersion/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 rulesThis “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, andPost*types all match the YAML keys in the fixtures and whatvalidator.goexpects to walk. Internal fields likeBuildRefContentandManifestItemsbeingyaml:"-"is the right choice to keep loader state out of the public schema. KeepingCondition.Operatoras 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 thegetCELEvaluatoruse ofsync.Oncegive 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+getStructFieldcovermap[string]interface{},map[interface{}]interface{}, and structs (with case‑insensitive field lookup), and returnFieldNotFoundErrorwith a usefulPathin all the expected failure modes (nil parents, wrong type, missing field). Together withIsFieldNotFoundnow usingerrors.As, this should give robust behavior for both direct evaluation and higher‑level helpers likeEvaluateSafein the CEL layer.
566-582:IsFieldNotFoundfix correctly handles wrapped errorsSwitching to
errors.Asmeans callers can reliably detectFieldNotFoundErroreven when it’s wrapped insideEvaluationErroror other error layers. This aligns with howEvaluateConditionWithResultreports lookup failures and makes the error model much easier to consume.
cfcabf5 to
b459ffe
Compare
There was a problem hiding this 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 usingconfig.CleanupTimeoutfor 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 usingCombinedOutput()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 fornildiscovery and document return semanticsRight now, a
nildiscoveryreturns(nil, nil). If a caller forgets to guard for that and iterateslist.Items, they’ll panic on anillist. To reduce footguns, consider either:
- returning an empty
*unstructured.UnstructuredListinstead ofnil, or- explicitly documenting that
nilindicates “no discovery configured” and must be checked by callers.The rest of the flow (single-resource wrapping into a list, or delegating to
ListResourcesfor selector-based discovery) looks good.internal/k8s_client/client_test.go (2)
9-43: Add a couple more assertions in the cluster-scoped discovery testTo fully exercise
DiscoveryConfigin the cluster-scoped case, you might also assertGetName()is empty andGetLabelSelector()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 nameThe
TestBuildLabelSelectorlogic is sound, but keying special behavior offtt.name == "multiple labels"is a bit brittle. Consider adding explicit expectation fields (e.g.,contains []string/requireComma bool) or a smallvalidatefunc 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
DiscoverResourcesexample (single resource), also check and handleerras you do in the first example for consistency.- Optionally use a different variable name (e.g.,
singleDiscovery) for the secondDiscoveryConfigto 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 environmentanddefer 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:IsAPIErrorandAsAPIErrorare identical.Both functions perform the same operation. While
AsAPIErroris 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
_ = adapterConfigand_ = apiClientassignments 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
⛔ Files ignored due to path filters (1)
go.sumis 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/execandstringsimports 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 cohesiveThe interface cleanly captures the needed discovery knobs, and the default
DiscoveryConfigplus its methods behave as expected (namespace/name/selector +IsSingleResourcebased solely onByName). Pointer receivers and default-zero semantics also look appropriate for how this will be used.
102-114:BuildLabelSelectorhelper is correct and aligns with Kubernetes label selector syntaxHandling of
nil/empty maps and buildingk=vcomma-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 wellThe addition of
DiscoverResources()to the architecture diagram and the short example under “Dynamic Client Operations” accurately reflects the new API surface and usage withDiscoveryConfig. The relationship betweenListResourcesandDiscoverResourcesis 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
publishAndWaitForMessagehelper 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-typeandhyperfleet.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
TestEvaluateGreaterThanandTestEvaluateLessThantests 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
TestNullHandlingandTestDeepNullPathtests 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
TestRealWorldScenarioeffectively 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
APIErrorstruct captures comprehensive context for debugging API failures, including method, URL, status, response body, retry attempts, and duration. TheError()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.
IsTimeoutproperly checks both HTTP 408 andcontext.DeadlineExceeded, which is important for client-side timeout detection.internal/criteria/types.go (3)
7-58: Clean operator type definitions.The
Operatortype with well-documented constants and utility functions (IsValidOperator,OperatorStrings) provides a solid foundation for the criteria evaluation system. TheSupportedOperatorsslice 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. TheGetNestedFielddelegation to a helper keeps the API surface clean while allowing complex path resolution.
99-115: Proper error implementation.
EvaluationErrorcorrectly implements botherrorand the unwrap interface forerrors.Is/errors.Assupport. TheError()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.
TestClientRetryandTestClientRetryExhaustedeffectively verify that the client retries on 5xx errors and properly reports attempt counts. Usingatomic.AddInt32ensures thread-safe counting in the concurrent test server handler.
269-297: Important: 4xx non-retry verification.
TestClientNoRetryOn4xxcorrectly 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.
TestAPIErrorInRetryExhaustedverifies the complete error flow: the client returns anAPIErrorwith 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/TOPICtoBROKER_SUBSCRIPTION_ID/BROKER_TOPICimproves 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:
- Loads configuration with version validation
- Verifies API base URL is set via environment
- 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 withrequirefor critical checks andassertfor 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
byNameandbySelectorsdiscovery 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
ManifestItemswith 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
ManifestItemsis validated for required Kubernetes fields, with clear error indexing.
670-680: LGTM!Clean test for the
ValidationErrorshelper methods and error message formatting.internal/config_loader/accessors.go (3)
12-79: LGTM! Comprehensive variable definition collection.The
GetDefinedVariablesmethod 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
normalizeToStringKeyMaphelper correctly handles bothmap[string]interface{}(yaml.v3) andmap[interface{}]interface{}(yaml.v2) for cross-library compatibility. The non-string key fallback usingfmt.Sprintfis 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
resolvePathfunction now correctly:
- Resolves both base and target paths to absolute, cleaned paths
- Uses
filepath.Relto compute the relationship- 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
SchemaValidatorimplementation. 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.HasPrefixfor 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, andEvaluateConditionSafeensures 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
b459ffe to
475a764
Compare
There was a problem hiding this 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
labelMapon line 185 actually holds alabels.Selector(returned bymetav1.LabelSelectorAsSelector), not a map. Consider renaming it tolabelSelorselectorfor 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
BuildLabelSelectorwill 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 theimageTagparameter for consistency.The image tag is hard-coded to
v1.0.0, but the PR introduces animageTagparameter (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/refskeys), 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
templateVarRegexcurrently 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 forresourcesin CEL env may not match the intent
initCELEnvintends to “always addresourcesas a map” via:if !addedRoots["resources"] { options = append(options, cel.Variable("resources", cel.MapType(cel.StringType, cel.DynType))) }However, when
GetDefinedVariables()includes keys likeresources.clusterNamespace, the loop above will already register a root variableresourcesascel.DynType, causingaddedRoots["resources"]to betrueand skipping theMapTyperegistration. That meansresourcesends up asDynTypeinstead 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 validationThe manifest validation correctly enforces presence of
apiVersion,kind,metadata, andmetadata.name, and ensuresapiVersion/kindare 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-stringapiVersion/kindas 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
kindandmetadata’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 initializingEvaluationContext.DatainSet
NewEvaluationContext()initializesData, but if a caller creates anEvaluationContextvia a zero-value struct literal,Datawill be nil andSet()will panic on assignment.You could make
Setmore 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 validationHere
Condition.Operatoris a plainstringandWhenExpr.ConditionsreusesCondition. Given thatconfig_loader.Validatoralready validates operators usingcriteria.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 forOperatorExistswith missing fields
EvaluateConditionWithResultalways callsGetNestedFieldfirst and treats any error (includingFieldNotFoundError) as fatal, returning anEvaluationErrorbefore theOperatorExistsbranch is reached. As a result:
EvaluateConditionSafe(..., OperatorExists, ...)will returnfalsefor missing fields (by swallowing the error), which matches a typical “exists” interpretation.EvaluateConditionsWithResultwill instead bail out with an error as soon as it hits a missing field, even forOperatorExists, instead of treating the condition as simply “not matched”.If your intent is “
existsshould never surface an error for missing fields, onlyfalse”, you might want to special‑caseFieldNotFoundErrorwhen handlingOperatorExists, 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 fieldsThe
getNestedField/getFieldRecursive/getFieldValuepipeline correctly handles:
map[string]interface{}andmap[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
getStructFieldcallingf.Interface()without checkingf.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(), nilThis 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
⛔ Files ignored due to path filters (1)
go.sumis 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
Discoveryinterface 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
DiscoverResourcesmethod 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:
- Check
publishTestMessageserrors: If publishing fails silently, the test will timeout without indicating the root cause was a publish failure rather than a subscriber issue.- 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.Terminatemakes this safe to call multiple times.
136-147: LGTM! Setup failure cleanup pattern is correct.The deferred cleanup with
setupSuccessflag ensures containers are terminated if setup fails, preventing resource leaks. The pattern correctly:
- Registers cleanup before assertions (line 140)
- Guards cleanup with both
!setupSuccessand non-nil container checks- Sets
setupSuccess = trueonly 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.1and 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
imageTagparameter 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
resolvePathfunction properly prevents directory traversal attacks by:
- Converting both base and target paths to absolute, clean paths
- Using
filepath.Relto 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
ValidateStructuremethod 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
byNameorbySelectors. 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()andfindExtractByAs()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
CleanupLeakedContainersfunction 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-clientat line 290 may be inconsistent with the actual package path. Verify whether the package directory is namedinternal/k8s-client(with hyphen) orinternal/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
refand arrayrefspatterns 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
loaderConfigis 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
baseDirderivation 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
cfgoptions 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 usingruntime.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.HasPrefixfor 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
CELResultstruct clearly documents when fields are populated and provides convenientHasError()andIsSuccess()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.DynTypefor nil and unknown types.
131-209: LGTM: Robust error-capturing evaluation with clear categorization.The
EvaluateSafefunction 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: Nestedexistsoperator 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 byEvaluateSafe()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 withstrconv.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
EvaluateSafeat 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: Excellenthas()semantics documentation through tests.This test effectively documents the behavior of
has()with nested paths:
has(data.level1.level2.value)succeeds when all intermediates existhas(data.level1.level2.missing)succeeds with false when parent exists but leaf doesn'thas(data.level1.nonexistent.value)errors when intermediate doesn't existThis serves as both test coverage and behavioral documentation.
internal/config_loader/validator.go (5)
18-50: Validation error aggregation is clear and usableThe
ValidationError/ValidationErrorstypes 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 reasonableNil-config guarding plus running all validators and returning a single aggregated
ValidationErrorsinstance gives callers both safety and comprehensive feedback. The choice to continue afterinitCELEnvfailure (while recording the error) is also acceptable since CEL-specific checks then effectively no-op whencelEnvis nil.
111-144: Operator validation correctly delegates to criteria helpersCondition operator validation over preconditions and post
whenblocks is wired correctly and usescriteria.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
isVariableDefinedcorrectly supports exact matches, root-variable checks (e.g.,clusterDetails.status.phasevsclusterDetails), and the specialresources.<alias>pattern used for discovered resources. This looks aligned with howGetDefinedVariables()is described to populate resource aliases; no issues spotted.
493-498: PublicValidatewrapper keeps API backward compatibleExported
Validate(*AdapterConfig)delegating toNewValidator(config).Validate()preserves the previous entry point while enabling the richerValidatortype for more advanced use. Nice separation of concerns.internal/criteria/types.go (2)
7-39: Operator constants and helpers are consistent with CEL conversionThe
Operatorconstants andSupportedOperatorsslice line up with whatConditionToCELand 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’sError()message includes both the field and message (and inner error when present), andUnwrap()exposes the underlying cause forerrors.Is/errors.As. This works well withIsFieldNotFoundand other higher-level helpers.internal/config_loader/types.go (3)
3-39: Core config model matches the described adapter surface
AdapterConfig,AdapterConfigSpec,AdapterInfo, andHyperfleetAPIConfigform 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 rawBuildstructures,BuildRef/BuildRefContent, andFetchExternalResourcegives you a lot of room for advanced parameter derivation without over-constraining the schema. The separation of on-disk refs from loaded content viayaml:"-"is also clean. Looks good.
113-121: Resource / Discovery definitions integrate cleanly with k8s discovery helpersThe
ResourceandDiscoveryConfigstructs align withinternal/k8s_client/discovery.goexpectations (namespace, name vs selectors, label selector as string map), andManifestItemsbeing 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
NewEvaluatorsafely handles a nil context by creating a default one, andgetCELEvaluatorusessync.Onceto lazily initialize and cache theCELEvaluator. This avoids repeated CEL env construction while keeping the API simple.
313-385: Operator implementations cover expected cases; error reporting is clear
evaluateEquals/evaluateIn/evaluateContainsusereflect.DeepEqualand kind checks appropriately to handle maps, slices/arrays, and strings.evaluateGreaterThan/evaluateLessThandelegate tocompareNumbers/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
FieldNotFoundErrorprovides a simple structured error, andIsFieldNotFoundnow useserrors.Asto detect it even when wrapped (e.g., insideEvaluationError). That matches how higher‑level code wants to treat “field missing” as a distinct class of error and resolves the earlier limitation.
475a764 to
4886d24
Compare
There was a problem hiding this 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 frameworkTest Coverage
• Unit tests for all config validation scenarios
• CEL expression evaluation test suite
• API client integration tests with containerized endpoints
• E2E configuration loading testsSummary 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: | |||
There was a problem hiding this comment.
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 ':'.
There was a problem hiding this 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-upThe local
gvkstruct 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 usingschema.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 cleanupThe per-attempt termination + force cleanup (lines 157-171) and the final
t.Cleanupwith 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
ctxin the retry failure block shadows the outerctxand differs from thecleanupCtxnaming 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; doubleTerminateis acceptable but slightly redundantThe changes here make the envtest lifecycle much safer:
TestEnvPrebuilt.Cleanupnow 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.SetupTestEnvPrebuiltadds asetupSuccessflag and a deferred block that force-terminates the container when setup fails mid-way, giving you "cleanup before assertions" semantics.Given that
testutil.StartContaineralready registers at.Cleanuphook that callsTerminateon the same container, you now have up to three termination call sites (the setup defer,TestEnvPrebuilt.Cleanup, and thet.Cleanuphook). BecauseTerminateis 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.CleanupplusTestEnvPrebuilt.Cleanupand 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.namein template contextThe added
hyperfleet.io/resource-type: "job"andhyperfleet.io/managed-bylabels align with the discovery expectations from the adapter config, and the rest of the manifest looks fine.One thing to verify: ensure
.metadata.nameis actually present in the render context sohyperfleet.io/managed-bydoes 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 shadowinglabelSelectorwhen converting to a selectorThe 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 codeThe new discovery and low‑level listing examples are clear and match the client API, but the header refers to
k8sclientwhile the examples usek8s_clientas 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 configurationYou 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 stringsThis 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.ConditionDeffrom config conditionsMultiple tests rebuild the same slice pattern from
precond.Conditionsinto[]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
⛔ Files ignored due to path filters (1)
go.sumis 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 semanticsThe immediate retry after an
AlreadyExistserror 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 anAlreadyExistserror 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
AlreadyExistsonly 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 ofSetGroupVersionKindwith localgvkAll updated
SetGroupVersionKindcalls now consistently use the localgvkconstants for core resources, which keeps the tests explicit about the resource type while remaining decoupled from internal helpers. This matches the inlineapiVersion/kindin 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 verificationThe migration of client calls to use GVK patterns should be verified to ensure complete removal of
CommonResourceKindsreferences 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 useThe new
forceCleanupContainerandCleanupLeakedContainershelpers 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
dockerthenpodman, matching the documented expectations.forceCleanupContainernow 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/nullerror 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_IDtoBROKER_SUBSCRIPTION_IDis 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_ID→BROKER_SUBSCRIPTION_ID,TOPIC→BROKER_TOPIC) establish a consistentBROKER_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 goodUsing
schema.GroupVersionKindon 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‑targetedThe 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 APIThese 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*WithResultpaths. 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 levelEvent 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
existsoperator on nested paths (lines 307-314), errors are intentionally captured in the result per author's confirmation, allowing callers to handle missing fields appropriatelyinternal/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-structuredThe 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 behaviorThese 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 schemaThe 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 handlingThe 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 usageThe type definitions (adapter/spec, parameters, preconditions, resources, discovery, post actions) and their YAML tags form a consistent schema, and the
ConditioncustomUnmarshalYAMLcorrectly handles bothvalueandvalueswith clear precedence; I don’t see structural issues here.internal/criteria/evaluator.go (1)
327-345: Verify nil-safety inevaluateInandevaluateContainsagainst actual implementationThe review's specific claim that
reflect.ValueOf(nil).Kind()panics is incorrect. Go's reflect package returns an invalidreflect.Valuefor nil, and callingKind()safely returnsreflect.Invalidwithout panicking.However, the broader concern about nil-safety is still valid. Calling other reflect methods like
Len(),Index(),Interface(), orIsNil()on invalidreflect.Valueobjects will panic. The review's snippet showslist.Len()andlist.Index(i)called immediately after the kind check, which would panic ifexpectedListis nil.To properly assess the issue:
- Confirm whether the implementations call
Len(),Index(),Interface(), or similar methods on potentially-invalid reflect Values- Verify whether nil inputs are actually reachable through normal caller paths or only through edge cases
- If panics are reachable, the suggested nil checks are appropriate; if not, the issue may be lower priority or moot
731aa97 to
782134c
Compare
There was a problem hiding this 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
ByNameandLabelSelectorpopulated (to verify precedence/behavior)- All fields empty (to verify default behavior)
ByNameset withoutNamespace(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 checkexitErr.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:
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.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
publishTestMessagesplus the bufferedmessageReceivedchannel gives a clean, race‑free way to wait for readiness. If you ever see CI timeouts here, you might optionally logctx.Err()inside the helper before returningfalse, 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 withValidateAdapterVersionthey give a nice, centralized schema gate.- The file‑reference layer (
validateFileReferences,loadFileReferences, andresolvePath) now properly constrains paths tobaseDirand produces descriptive errors for missing/dir paths, which addresses path traversal concerns.Two minor follow‑ups you might consider:
- In
validateResourcesyou loop asfor i, resource := range v.config.Spec.Resourcesand then callvalidateResourceDiscovery(&resource, path). That pointer is to a copy, which is fine today becausevalidateResourceDiscoveryis read‑only, but it could be surprising if someone later tries to mutate the resource there. Either acceptResourceby value invalidateResourceDiscoveryor iterate by index and pass&v.config.Spec.Resources[i]for future‑proofing.validateParamscurrently only walksconfig.Spec.Params. If you wantspec.post.paramsto 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 forSpec.Post.Paramsor factoring a shared helper; right now, post params are only checked for file existence when they usebuildRef.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]andbuildRefcontent), 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 ofbuiltinVariablesto 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 != nilblock.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
⛔ Files ignored due to path filters (1)
go.sumis 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_ID→BROKER_SUBSCRIPTION_IDandTOPIC→BROKER_TOPICintroduces 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
gvkstruct 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
SetGroupVersionKindand client method calls correctly reference the localgvkdefinitions. 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 ./...andgo mod tidysogo.sumand the module graph stay in sync.pkg/errors/api_error.go (1)
14-150: Rich API error type is well‑designed and integrates cleanly witherrors.Is/As.The
APIErrorstruct, helper predicates, andIsAPIErrorwrapper 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
baseDirinLoadand prependingWithBaseDir(while still allowing override) is a nice touch, andrunValidationPipelinecleanly 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.reftemplate), 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:
hyperfleetApiVersionis markedrequired: truebut also hasdefault: "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, andAdapterConfigSpecstructs 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, andKubernetesConfigstructs correctly model their respective configuration sections.
46-71: LGTM - Parameter and FetchExternalResource types support flexible configuration.Good use of
yaml:"-"tag onBuildRefContentto prevent serialization of runtime-populated content. Theinterface{}types forBuildandDefaultallow schema flexibility.
73-104: LGTM - Precondition and API call types are comprehensive.The
Precondition,APICall,Header, andExtractFieldstructs 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
UnmarshalYAMLimplementation correctly prioritizesvaluesovervaluewhen both are specified, providing backward compatibility and flexibility.
163-186: LGTM - Post-processing types are well-defined.The
PostConfig,PostAction,WhenExpr, andManifestReftypes properly model the post-processing configuration with conditional execution support.
141-161: Potential naming collision withk8s_client.DiscoveryConfig.Both
config_loader.DiscoveryConfigandk8s_client.DiscoveryConfigexist with similar but not identical structures. Thek8s_clientversion usesByNameandLabelSelectorstring fields, while this version usesByNameandBySelectorswith a nestedSelectorConfig. This may cause confusion when converting between types.Consider either:
- Renaming this type to
ResourceDiscoveryConfigfor clarity- Adding conversion methods to bridge between the two types
- 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
GetXxxByNamemethods 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()andResourceNames()correctly pre-allocate slices and return names in order.
178-185: LGTM - ParseTimeout handles empty string correctly.The method returns
0, nilfor 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, andUnmarshalManifestmethods properly handle both singlerefandrefsarray formats, with appropriate nil-safety and type assertions.
259-277: LGTM - Robust map normalization for YAML compatibility.
normalizeToStringKeyMapcorrectly handles bothmap[string]interface{}(yaml.v3) andmap[interface{}]interface{}(yaml.v2) formats, with a fallback for non-string keys usingfmt.Sprintf.configs/adapter-config-template.yaml (2)
123-128: LGTM - NewimageTagparameter follows established patterns.The parameter is properly configured with source, type, default value, and description. Setting
required: falsewith a default is appropriate for optional configuration.
384-388: Verify authentication handling forreportClusterStatusAPI call.The
reportClusterStatuspost-action has noheadersconfigured in the YAML. Confirm whetherAuthorizationandContent-Typeheaders 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 - ComprehensiveTestEvaluateEqualscovers 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 inTestEvaluateGreaterThan.Good coverage overall. The zero-value tests (lines 311-327) are particularly valuable for catching edge cases.
414-468: LGTM -TestEvaluateExistscovers various types comprehensively.Tests correctly verify that empty strings, empty slices, empty maps, and nil values return
false, while non-empty values and numbers returntrue.
820-863: LGTM -TestEvaluateConditionsWithResultvalidates result structure thoroughly.Tests verify that extracted fields are populated even when conditions fail, and that
FailedConditionindex is correctly set.
959-1018: LGTM - Excellent null-safety test coverage.
TestNullHandlingthoroughly 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.
TestDeepNullPathvalidates 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_PATHenvironment variable alternative.
62-91: LGTM - Robust configuration loading with proper error handling.The startup sequence correctly:
- Loads config with version annotation
- Validates API base URL presence
- Logs configuration details
- 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
_ = adapterConfigand_ = apiClientis 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/jsonfor 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.Quotecorrectly 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
existsoperator for nested paths generateshas(a.b.c)which can error if intermediate paths are null. The author confirmed this is intentional - errors are captured inCELResult.ErrorandCELResult.ErrorReasonrather than panicking, allowing callers to handle missing fields appropriately viaEvaluateSafe().
131-140: Verify: Empty expression evaluates to true.An empty or whitespace-only expression returns
truewithMatched=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.HasPrefixeliminates 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.Asproperly detectsFieldNotFoundErrorwrapped inside other errors likeEvaluationError, addressing the previous review concern.
57-64: LGTM! Efficient lazy CEL evaluator initialization.Using
sync.Oncefor 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
05de19f to
bbb45de
Compare
There was a problem hiding this 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: AlignNewSubscriberexample with the real function signature.The basic example shows
subscriber, err := brokerconsumer.NewSubscriber(subscriptionID), but the integration tests usesubscriber, subscriptionID, err := broker_consumer.NewSubscriber("")and rely on env-based subscription resolution. Please update the README to match the actualNewSubscribersignature/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.namespaceis 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 minimumBoth in
StartContainerandStartSharedContainer, 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.
WithDefaultHeaderaccessesc.config.DefaultHeaderswithout checking ifc.configis nil. While normal usage (withNewClient) ensures config is initialized first, direct usage of options could panic ifWithConfig(nil)is passed before this option.Given the functional options pattern always initializes config in
NewClientbefore 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 tot.Setenvfor test-scoped environment variablesUsing
t.Setenvhere 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 viat.Cleanupinstead of returning acleanupfunc, 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 useslocalhost/hyperfleet-integration-test:latestspecifically (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:"; thenAlternatively, 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 selectorand// Example 2: Discover single resource by nameto improve readability.test/integration/k8s_client/helper_selector.go (1)
54-67: Prefer standard librarystrings.Containsover custom implementation.The custom
containsandcontainsSubstringfunctions reimplement functionality available in the standard library. Usingstrings.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
InsecureSkipVerifyis necessary for envtest's self-signed certificates, the TLS configuration should still specify a minimum version. Consider settingMinVersion: tls.VersionTLS12to 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.Configdoesn't directly exposeMinVersioninTLSClientConfig, you can create a customTLSClientConfigif needed. However, since this is a test environment andInsecure: truealready 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 duplicationThe
t.Cleanuppath that falls back toforceCleanupContainer, plus theforceCleanupContainerNoTestvariant forTestMain, give a good last‑resort escape hatch whenTerminatefails. The docker/podman usage viaexec.CommandandCombinedOutputis 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 idempotentThe
SharedContainershape andGetEndpoint/Cleanupmethods make reuse inTestMainstraightforward, 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
StartSharedContainernicely encapsulates a shared container lifecycle (retries, port wiring, explicit cleanup inTestMain). Two improvements worth considering:
- Fallback when terminate during retries fails
In the retry loop, ifcontainer.Terminatefails for a partially started container, there’s currently no force‑cleanup, so a broken attempt could leak a container. MirroringStartContainer’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() + }
- Reuse or document
CleanupTimeoutsemantics
ContainerConfig.CleanupTimeoutis 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/orSharedContainer.Cleanuppaths, 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 theexistsoperator in the template header.Later in this template you use
operator: "exists"(e.g., forvpcId), but the “Supported operators” comment only lists equals/notEquals/in/notIn/contains/greaterThan/lessThan. It’d be clearer to includeexiststhere as well.-# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThan +# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThan, existscmd/adapter/main.go (1)
182-222: API client creation helper is well-structured.The
createAPIClientfunction 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:evaluateExiststests 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
evaluateExiststreats0as 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
SchemaValidatorfor 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
SchemaValidatordirectly 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.,appendor 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
NewClientdoesn'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
httpClientstruct to testcalculateBackoff. 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: MissingFieldWhenconstant.The
whenfield is used in the YAML configuration (e.g.,test/testdata/adapter_config_valid.yamllines 123, 202) but there's no correspondingFieldWhenconstant 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
EvaluateConditionsWithResultis 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 sincecond.Operatoris already of typeOperator(per theConditionDefstruct 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
toFloat64covers all standard Go numeric types, andevaluateExistscorrectly handles various zero/empty values.Optional: Clarify the map key checking logic.
At lines 360-371 in
evaluateContains, the special handling forinterface{}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
⛔ Files ignored due to path filters (1)
go.sumis 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
gvkstruct 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
TestEnvPrebuiltcorrectly implements theTestEnvinterface, 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
createDefaultNamespaceNoTestfunction 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 consistentThe 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 viaancestor=filter is reasonable
CleanupLeakedContainers’s strategy of usingdocker|podman ps -a --filter ancestor=...and thenrm -fon 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
publishAndWaitForMessagewith 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,
SupportedOperatorshelpers,EvaluationContext(withMerge/nested access), andEvaluationError(includingUnwrap) 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
NewCELEvaluatorsetup,Evaluate/EvaluateSafelayering with categorized error reasons, and theConditionToCEL/ConditionsToCELhelpers align with the documented semantics (errors captured inCELResultand 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
_ = adapterConfigand_ = apiClientassignments 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/assertandtestify/requireis 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
evaluateEqualsfunction.
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
greaterThanoperator. Similar coverage exists forlessThan(lines 382-398).
959-1018: Excellent null-safety test coverage.The
TestNullHandlingfunction 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:toFloat64test 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/notInoperatorsThis aligns well with the
criteria.IsValidOperatorfunction 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
resolvePathfunction properly:
- Gets absolute path for baseDir
- Resolves the target path (handling both absolute and relative)
- Uses
filepath.Relto check containment- 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
bySelectorsis 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
localCtxandlocalEvaluatorin 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
containsoperator on maps (checking for label/annotation key existence) is a common Kubernetes use case. The tests cover bothmap[string]interface{}and typedmap[string]stringscenarios.
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 clustersThe 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-1andlessThan maxNodes+1to 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 ofFieldResourcesconstant 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) andrefs(array) patterns.UnmarshalManifestprovides clear error messaging with type information when the manifest cannot be converted.
272-290: LGTM - handles yaml.v2/yaml.v3 compatibility well.The
normalizeToStringKeyMaphelper correctly handles bothmap[string]interface{}(yaml.v3) andmap[interface{}]interface{}(yaml.v2) types, with a fallback tofmt.Sprintffor 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
valueandvalueskeys in a condition produces an appropriate error.internal/config_loader/validator.go (7)
19-51: LGTM - clean validation error types with proper formatting.
ValidationErrorandValidationErrorsprovide structured error reporting. TheError()method onValidationErrorsproduces 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
existsoperator doesn't require a value, and enforces list values forin/notInoperators 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
isVariableDefinedmethod properly handles root variable lookups and special-casesresources.<alias>paths. This enables correct validation of expressions likeresources.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 andcel.DynTypefor 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
validateK8sManifestmethod checks for required Kubernetes fields (apiVersion,kind,metadata) and validates thatmetadata.nameis present. Empty string checks forapiVersionandkindprevent 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
LoaderOptionpattern withloaderConfigis idiomatic Go. Options are simple and focused on single responsibilities.
88-111: LGTM - robust file loading with automatic base directory resolution.The
Loadfunction correctly handles empty paths by falling back to the environment variable, derives the absolute path for base directory calculation, and prependsWithBaseDirto allow user override.
113-130: LGTM - clean separation between parsing and validation.
Parsecorrectly 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.Randwas addressed by removing it from the struct. The client now uses the package-levelrand.Float64()which is concurrency-safe.
117-145: LGTM - proper client initialization with environment fallback.
NewClientcorrectly 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
retryAttemptsto 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
APIErrorwith all relevant details
248-262: LGTM - clean URL resolution logic.
resolveURLcorrectly 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 toDo. TheBaseURL()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
NewClientconstruction 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/Assemantics, 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
DefaultHeadersto 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
RetryAttemptsandRetryBackoffcorrectly 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
IsRetryablemethod 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
BackoffStrategytype.
51-87: LGTM! Comprehensive precondition examples.Good coverage of both structured conditions (with operators like
inandexists) 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 (
whenclause)- 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.
FieldHeaderValueandFieldValueboth 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
omitemptytags for optional fields andinterface{}for flexible schema elements likeDefault.
60-95: LGTM! Proper mutual exclusivity validation for Build/BuildRef.The
Validate()method correctly enforces that exactly one ofBuildorBuildRefmust be set. The documentation clearly explains the constraint.
97-127: LGTM! Flexible precondition structure.The
Preconditiontype supports both API-call-based preconditions with data capture and pure CEL expression preconditions. The string type forTimeoutallows flexible duration parsing (e.g., "10s", "1m").
129-167: LGTM! Clean dual-key support for conditions.The custom
UnmarshalYAMLelegantly handles bothvalueandvaluesYAML keys while properly rejecting configurations that specify both. This provides good UX flexibility for config authors.
169-206: MissingWhenfield onPostActionandResourcetypes.Based on the test YAML (lines 123-127 and 202-205 of
adapter_config_valid.yaml), bothResourceandPostActionsupport awhenclause 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.Oncefor 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. TheevaluateEqualsimplementation correctly handles nil cases and usesreflect.DeepEqualfor 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. TheIsFieldNotFoundhelper correctly useserrors.Asto detect wrappedFieldNotFoundErrorinstances, addressing the concern from previous reviews.
95bdc0e to
6f0fed4
Compare
There was a problem hiding this 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: FixclusterConfigMapnamespace templating (currently uses status.phase)For the
clusterConfigMapresource, the manifest’s namespace is:metadata: name: "cluster-config-{{ .clusterId }}" namespace: "{{ .resources.clusterNamespace.status.phase }}"Using
resources.clusterNamespace.status.phasehere will generate namespaces like"Active"rather than"cluster-<id>", and will not match the discovery config below, which looks for the ConfigMap incluster-{{ .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 includeexistsThe header comment currently says:
# Supported operators: equals, notEquals, in, notIn, contains, greaterThan, lessThanBut the criteria layer now supports and actively uses an
existsoperator (see thevpcIdcondition in this same file andOperatorExistsininternal/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 minimumIn
StartContainerandStartSharedContainer, 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.CleanupTimeoutwith 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:existsoperator 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 byEvaluateSafeand 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 andIsFieldNotFoundbehavior look solidThe combination of
getFieldRecursive,getFieldValue,getStructField, andFieldNotFoundErrorgives clear error signaling for missing or inaccessible fields, andIsFieldNotFoundnow correctly useserrors.Asto detect wrappedFieldNotFoundErrorinstances. This should integrate cleanly with higher‑level error handling likeEvaluateSafe.
🧹 Nitpick comments (18)
pkg/logger/logger_test.go (1)
185-193: Strengthened context-value assertions look good; consider avoiding duplicate errors whenvalis nilThe added checks correctly verify both presence and correctness of the context value, and using
%vin the messages is appropriate for mixed types.To avoid emitting two error messages when
valisnil, you could turn the secondifinto anelse 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: Simplifycontainsimplementation for readabilityThe 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 == ""returningtrue) 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 constantUsing 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 correctThe new
publishAndWaitForMessagehelper is a solid improvement over fixed sleeps: it actively retries publishes until the handler signals viamessageReceivedor 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 returningfalse, but the current behavior is functionally sound.configs/templates/job.yaml (1)
1-28: Optionally parameterize the Job image withimageTagThe template looks correct and matches the discovery labels expected by
validationJob. For consistency withconfigs/adapter-config-template.yaml(which defines animageTagparam) 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 initializeEvaluationContext.DatainSet/MergeRight now
SetandMergeassumec.Datais 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
EvaluationContextwithout usingNewEvaluationContext(e.g.var c EvaluationContextor&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
NewEvaluationContextis 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 logicBoth
forceCleanupContainerandforceCleanupContainerNoTestimplement nearly the samedocker/podmanrm -floop: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
forceCleanupContainerandforceCleanupContainerNoTestcan just wraplogwitht.Logforprintlnas 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 makinggetProjectRootless dependent on substring matching
getProjectRootfinds the project root by doing astrings.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
projectNameas 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 toprojectName, or just rely on relative paths from the module root (sincego 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 newimageTagparam in the template integration test
TestLoadTemplateConfigdoes 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: falseyou 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 aMinVersion. 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 internalbuiltinVariablesslice 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 Requestsinternal/hyperfleet_api/client.go (2)
276-307: Log the resolved URL instead of (or in addition to) the raw request URL
doRequestlogsreq.URLrather than theresolvedURLactually sent to the server, which can make debugging base‑URL issues harder. Consider swapping toresolvedURL(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
doRequestalways wraps the parent context withcontext.WithTimeoutusingc.config.Timeout(orreq.Timeout), andNewClientalso setshttp.Client.Timeout = c.config.Timeoutwhen constructing the default client. WhenTimeoutis zero or a customhttp.Clientis injected viaWithHTTPClient, this can yield surprising behavior (immediate cancellation forTimeout <= 0, orWithTimeouthaving no effect on a caller‑supplied client).Consider either:
- Treating
Timeout <= 0as “no per‑request deadline” (skipWithTimeoutand rely on parent context / client timeout), and/or- Documenting that
WithTimeoutonly applies when you don’t also passWithHTTPClient.internal/hyperfleet_api/client_test.go (2)
16-17: Use a less ambiguous alias thanerrorsfor the API error packageThis test file aliases
github.com/openshift-hyperfleet/hyperfleet-adapter/pkg/errorsaserrors, which can easily be confused with the standard libraryerrorspackage (and differs from theapierrorsalias in production code). Consider renaming the alias here (for example,apierrors) for consistency and to avoid confusion.
30-55: Simplify environment handling inTestNewClientMissingBaseURLThe combination of
os.LookupEnv,t.Setenv,os.Unsetenv, and an extrat.Cleanupis more complex than needed and duplicates restoration logic thatt.Setenvalready provides. Sinceos.Getenvreturns""for both unset and empty values, you can just uset.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.Validateenforces 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.Namein 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 inEvaluationErrorfor consistent diagnostics
EvaluateConditionWithResultwraps field‑lookup failures and unsupported operators inEvaluationError, but returns raw errors from helpers likeevaluateGreaterThan,evaluateLessThan,evaluateIn, etc. Callers then see conversion/compare errors without field/operator context.For consistency and easier debugging, consider wrapping non‑nil
errfrom the helper section in anEvaluationError(includingField,Operator, andExpectedValue), similar to the GetNestedField/unsupported‑operator paths.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis 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_IDonce and passing it explicitly to bothNewSubscriberandSubscribe. 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 listsBROKER_SUBSCRIPTION_IDas 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 inTestLoggerChainingrestores proper test structureThe corrected closing brace at the end of
TestLoggerChainingnow 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 appropriateThe compile-time assertion
var _ TestEnv = (*TestEnvPrebuilt)(nil)is idiomatic Go and correctly ensuresTestEnvPrebuiltcontinues to satisfyTestEnvas 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 behaviorThe README now clearly explains client/request options, retryable status codes (all 5xx plus 408/429), and the
WithBodyvsWithJSONBodydistinction including theContent-Typedefault. 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 criteriaThis 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 invalidgo.yaml.inmodule paths; should begopkg.in/yamlLines 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 // indirectThe correct path is
gopkg.in/yaml(as used in the direct dependencygopkg.in/yaml.v3 v3.0.1). These entries will cause module resolution to fail. Remove them and rungo mod tidyto restore correct indirect dependencies automatically.
3-3: Verifygo 1.25.0directive matches your actual toolchainThe module declares
go 1.25.0. Using this version is valid only if your developers and CI are actually running Go ≥1.25; otherwisegocommands 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-testandtest-integrationtargets, 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
gvkconstants and migration toGetSharedEnv(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
TestMainimplementation properly manages the shared envtest container lifecycle with appropriate checks for short tests, skip flags, container runtime availability, and cleanup. TheGetSharedEnvhelper correctly usest.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
resolvePathfunction correctly validates that resolved paths remain within the base directory usingfilepath.Reland 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
SchemaValidatorprovides 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.
ParseTimeoutcorrectly returns0, nilfor empty timeout (allowing caller to use default), andGetBaseURLproperly 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) andmap[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
ValidationErrorandValidationErrorstypes provide clear error context with path information and a clean multi-error format. TheHasErrors()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 meansvalidateCELExpressions()will silently skip CEL validation (line 362-364 checksv.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/templateparsing 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
DynTypeappropriately for unknown types at validation time- Ensures
resourcesmap is always available
439-465: Inline manifest withrefkey is not validated after loading.When a manifest has a
refkey (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 amap[string]interface{}replacing the ref structure.Verify that after
loadFileReferencesruns, the manifest is properly re-validated. If the loaded content replacesresource.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), validatesmetadata.namepresence, and ensuresapiVersionandkindare 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
ValidHTTPMethodsListavoids 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
Loadfunction correctly:
- Falls back to environment variable when path is empty
- Converts to absolute path for reliable directory extraction
- Prepends
WithBaseDirto allow user override- Delegates to
Parsefor actual processing
140-187: LGTM - Well-structured validation pipeline with clear ordering.The pipeline follows a logical order:
- Core structural validators (always run)
- Adapter version validation (optional)
- File reference validation and loading (when baseDir set)
- 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.
CELResultcleanly separates success/error states withError/ErrorReasonfields 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 totrue- verify this is intentional.When
expressionis empty after trimming, the method returnsMatched: 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.
formatCELValuecorrectly:
- Uses
strconv.Quotefor 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.
ClientConfigcovers all necessary HTTP client settings, andDefaultClientConfig()initializes all fields with sensible defaults including an empty (but initialized)DefaultHeadersmap.
72-152: LGTM - Clean request options pattern with per-request overrides.The
Requesttype correctly uses pointers for optional override fields (RetryAttempts,RetryBackoff) to distinguish "not set" from zero values. The functional options are well-designed andWithJSONBodyconveniently sets both body and Content-Type.
215-237: LGTM - Clean and complete client interface.The
Clientinterface provides all essential HTTP methods with consistent signatures, context support, and theBaseURL()accessor for introspection. The use of variadicRequestOptionfor optional configuration is idiomatic.internal/criteria/cel_evaluator_test.go (1)
11-571: Comprehensive and targeted CEL evaluator test coverageThis 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.
There was a problem hiding this 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' endifinternal/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:
- Asserting that the update should fail (if that's the expected behavior for controller-runtime), or
- 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 thecontainsfunction.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
*TestEnvPrebuiltdoesn't check theokboolean. 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
InsecureSkipVerifyis necessary for envtest's self-signed certificates, consider addingMinVersion: 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 makinggetProjectRootmore 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 fromfilepath.Dir(filename)until you find ago.modor a directory namedprojectName, 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 inresolvePathlooks solid; consider caching base path once.The new
resolvePathcorrectly normalizesbaseDir, rejects paths that escape it viafilepath.Rel, and surfaces clear errors, which closes the earlier traversal hole. The only nit is thatfilepath.Abs/filepath.Cleanforv.baseDirare recomputed on every call; if you expect many manifest/buildRef files per config, you could cache a pre‑cleaned base path onSchemaValidator(e.g., in a separate field initialized inNewSchemaValidator) to avoid repeated work. Behavior would stay identical.internal/criteria/cel_evaluator.go (1)
229-241: Clarify or tightenEvaluateBoolsemantics for non‑boolean results.
EvaluateBoolreturnsresult.Matchedwhen the CEL value is not abool, which means expressions like"foo"or a non‑empty list will be treated astruerather 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.Valueis notbool, or- documenting that
EvaluateBooldegrades to “non‑empty means true” semantics for non‑boolean results.internal/hyperfleet_api/client.go (1)
165-246: Document thatRetryAttemptsis clamped to a minimum of 1.
DonormalizesretryAttemptsso any value< 1results 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 theClientConfig.RetryAttemptsandWithRequestRetryAttemptsdocs/comments to state that values< 1are 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.
forceCleanupContainerandCleanupLeakedContainerscallexec.Command(...).Run/CombinedOutput()directly. Ifdocker/podmanwere to hang (e.g., daemon issues), tests could block indefinitely in a cleanup path. As a small resilience improvement, consider usingexec.CommandContextwith 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
⛔ Files ignored due to path filters (1)
go.sumis 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 forTestLoggerChainingThe 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=trueflag 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-testandtest-integrationtargets are properly marked.PHONY, clearly documented with emojis and descriptions, and delegate to bash scripts. The parameterization ofTEST_TIMEOUTintest-integrationis good for consistency with unit test timeout.Verify that the referenced bash scripts (
scripts/build-integration-image.shandscripts/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 cleanbefore 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
NewSubscribersignature and correctly demonstrates using the resolvedsubscriptionIDreturn value for the subsequentSubscribecall.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
ListResourcesas a simpler alternative to the Discovery interface, with clear usage examples.
61-73: Verify DiscoveryConfig and DiscoverResources implementation.The documentation describes
DiscoveryConfigwithNamespace,LabelSelector, andByNamefields, and aDiscoverResources()method. Confirm these match the actual implementation indiscovery.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.ParseToLabelSelectorandmetav1.LabelSelectorAsSelectoris 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
NotFounderrors are handled elsewhere in this file, allowing callers to useapierrors.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
gvkstruct 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
waitForAPIServerReadyfunction 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
setupSharedTestEnvfunction properly:
- Uses
testutil.StartSharedContainerfor 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_IDand keepingBROKER_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.yamlandadapter_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--configintorunis straightforward and clear.Using a global
configPathflag and passing it intoconfig_loader.Loadinsiderun()keeps startup configuration simple; behavior with the fallbackADAPTER_CONFIG_PATHenv 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 fromSpec.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:createAPIClientcorrectly maps config fields and validates backoff strategy.Timeout parsing, retry attempts, and backoff strategy validation are all handled cleanly, and you correctly rely on
NewClientto 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
notInvariants) can be added incrementally as needed.internal/criteria/evaluator_scenarios_test.go (7)
1-9: LGTM - Well-structured test file.Good use of
stretchr/testifyfor 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
containsoperator on slices.
192-251: LGTM - Good coverage of map key containment.Testing both
map[string]interface{}andmap[string]stringis 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
imageTagparameter with a sensible default and environment variable source is a clean addition.
145-158: LGTM - Cleaner capture syntax.The new
captureblock withnameandfieldis more intuitive than the previousstoreResponseAs/extractapproach. 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
paramstopayloadsbetter 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 ito 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
refandrefsarray 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
IsTimeoutmethod correctly handles both HTTP 408 andcontext.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.Asfor 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
existsoperator not requiring a value andin/notInrequiring 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.DynTypesince 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
Validatefunction provides a simple entry point whileNewValidatorallows 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
Containsassertions 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.
LoadderivingbaseDirand prependingWithBaseDirbefore user options, plusrunValidationPipeline’s staged flow (schema/core checks → adapter version → file refs validate/load → semanticValidate), gives a clear separation of concerns and predictable error wrapping. The behavior also matches how the tests exerciseWithSkipSemanticValidationand file‑reference handling.internal/hyperfleet_api/types.go (1)
24-237: HTTP client type surface is clean and extensible.The separation between
ClientConfigdefaults, per‑request overrides onRequest, and theResponsehelpers (IsSuccess/IsRetryableetc.) reads well and should be easy to evolve. LeavingBaseURLunset inDefaultClientConfigand 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.Validatecleanly enforces the mutual exclusivity and presence ofbuildvsbuildRef, and the customCondition.UnmarshalYAMLcorrectly supportsvalue/valuesaliasing 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 theConditionsResult/EvaluationResultshapes all look consistent and maintainable; no issues in this part.
6f0fed4 to
a535220
Compare
There was a problem hiding this 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 markdownlintThe validation error example fence still lacks a language; adding
textkeeps 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 iffor the second check to avoid redundant error messages ifvalis 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: Customcontainsworks but could be simplifiedThe manual
contains/containsSubstringimplementation is correct, but it reimplements standard library behavior. Unless you’re avoiding thestringsimport on purpose, usingstrings.Containswould be simpler and easier to read.internal/k8s_client/discovery.go (1)
12-122: Discovery API and helper behavior look solid; minor GVK nitThe
Discoveryinterface,DiscoveryConfigmethods, andDiscoverResourcesflow (single-resource vs selector-based) are coherent and nil-safe, andBuildLabelSelector’s sorted output is exactly what you want for deterministic selectors. One optional improvement: when you set theGroupVersionKindon theUnstructuredList, you may want to use aKindof<Kind>List(e.g.,DeploymentList) to better match Kubernetes conventions, though in practice most callers won’t depend on the list’sKind.internal/k8s_client/README.md (1)
38-83: DiscoverResources documentation matches the API; minor package-path nitThe new sections showing
DiscoveryConfigusage for label- and name-based discovery align well with theDiscoverResourcesimplementation and should make the API easy to adopt. One small doc nit: elsewhere in this README you refer to the high-level package asinternal/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 refThe new
imageTagparam and the switch tocapturefields (clusterName, region, cloudProvider, vpcId, etc.) are wired consistently through the resources and preconditions, and thepost.payloads→postActionsflow forclusterStatusPayloadmatches the new post-processing model. Right nowclusterStatusPayloadRefis defined but not consumed by any postAction; if it’s intended purely as an example ofbuildRef, 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 scopeAligning the
CleanupTimeoutdefault with the enforced 60s minimum and explicitly terminating partially-started containers before retries significantly reduces the chance of leaked or stuck containers, and theforceCleanupContainer/forceCleanupContainerNoTestfallbacks are a reasonable best-effort whenTerminatefails. The newCleanupLeakedContainersandSharedContainerhelpers round out the test infra nicely; just make sure callers only pass test-specific images toCleanupLeakedContainers, 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
projectNamein 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.Tandt.TempDir()with relative paths from the test file- Search upward for
go.modfile to find the project root- Use
filepath.Abswith relative paths like../../..from the test file locationExample 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: BroadentemplateVarRegexto 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: setMinVersionon TLS config to satisfy scanners.For envtest you already intentionally use
InsecureSkipVerifyagainst 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 makingEvaluateBoolstricter for non‑boolean expressions.Right now, if the expression doesn’t return a
bool,EvaluateBoolfalls back toresult.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 returnfalseto 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 usingParsedirectly.
Loadcorrectly derivesbaseDirand runs file‑reference validation/loading, followed by semantic validation. When callingParsedirectly without aWithBaseDiroption, anybuildRef/manifest.refpaths 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
Parserequires an explicitWithBaseDirfor file refs, or- Having
runValidationPipelinefail when file refs are present butbaseDiris 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
WithHTTPClientis used, the custom client's timeout (if any) overridesc.config.Timeout. This is reasonable but worth documenting in theWithHTTPClientgodoc 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.ReadAllreads the entire response without bounds. A malicious or misconfigured server could return an extremely large body, causing memory exhaustion.Consider using
io.LimitReaderto 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
DiscoveryConfigdiffers frominternal/k8s_client.DiscoveryConfigintentionally: this version uses nestedSelectorConfigfor YAML ergonomics, while the k8s_client version uses a flatLabelSelectorstring 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
⛔ Files ignored due to path filters (1)
go.sumis 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 -cinvocation 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-testandtest-integration) depend on bash scripts in thescripts/directory (build-integration-image.shandrun-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/TOPICtoBROKER_SUBSCRIPTION_ID/BROKER_TOPICis 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_IDorTOPICoutside this template file- Confirm
BROKER_SUBSCRIPTION_IDandBROKER_TOPICare 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 tot.Setenvfor env vars is correct and idiomaticUsing
t.Setenvhere 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 robustThe longer context timeout and
publishAndWaitForMessagehelper 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 theprovider.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
/healthzwith bearer auth, andcreateDefaultNamespaceNoTesthelper (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 safeHasPrefixsource 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.v3dependency. 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
httpClientstruct is properly simplified (rand field correctly removed to use the concurrency-safe package-levelrand.Float64()).
36-105: Functional options are well-structured.The
ClientOptionpattern is idiomatic Go. The nil check inWithConfigand lazy initialization ofDefaultHeadersinWithDefaultHeaderare 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
APIErrorwrapping 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. TheBaseURL()accessor is a straightforward getter.internal/config_loader/types.go (5)
1-11: Root configuration structure is well-defined.The
AdapterConfigtype follows Kubernetes-style structure withapiVersion,kind,metadata, andspec. YAML tags are correctly applied.
36-42: Configuration fields use string types for flexibility.
TimeoutandRetryBackoffas 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 != nilwill 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
UnmarshalYAMLimplementation 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, andManifestRefare 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.Oncefor CEL evaluator caching avoids repeated environment creation overhead. The nil check inNewEvaluatorprovides a sensible default context.
66-90: Field access methods provide good API ergonomics.The trio of
GetField,GetFieldOrDefault, andGetFieldSafecovers strict, defaulting, and null-safe access patterns.HasFieldcorrectly 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/NotInis correctly applied.EvaluationErrorwrapping 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
fieldValueandneedleat lines 360-367IsValid()checks beforeKind()at lines 381-383 and 390-392- Item validity check before
Interface()at lines 412-414This addresses the previous review concerns about reflection panics.
440-461: evaluateExists correctly handles various types.The nil check at line 442 handles the
nilinterface case. TheIsNil()call at line 457 is safe because it's only reached forPtrorInterfacekinds, which supportIsNil().
478-508: Numeric conversion covers all standard types.The type switch handles all integer and float variants. Note that
uint64tofloat64conversion 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{}andmap[interface{}]interface{}) and structs. Case-insensitive fallback for struct fields improves usability.FieldNotFoundErrorprovides detailed context for debugging.
621-626: IsFieldNotFound correctly uses errors.As for unwrapping.This addresses the previous review concern.
errors.Asproperly detectsFieldNotFoundErroreven when wrapped insideEvaluationError.
a535220 to
207e14a
Compare
There was a problem hiding this 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 onAlreadyExistslooks correct; consider minor robustness tweaksThe new immediate retry when
isTopicAlreadyExistsError(err)is true makes sense here and should help with racy topic-creation between publisher and subscriber. Using the sameevtinstance is fine for tests, and failing hard on a second error viarequire.NoErroris 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_CMDvariable on Line 58 is never executed because theimagetarget (lines 154–162) exits early whenCONTAINER_RUNTIMEis "none", before attempting to useCONTAINER_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:containshelper is correct but could be simplified with stdlib helpersThe custom
contains/containsSubstringimplementation looks correct for all edge cases (including empty substrings and shorters), and safely guards against out-of-range slicing. Given this is test-only code, you might optionally simplify:
- Replace
containswithstrings.Containsand dropcontainsSubstring, or- For
isAlreadyExistsError, consider usingapierrors.IsAlreadyExists(err)fromk8s.io/apimachinery/pkg/api/errorsif 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
ByNameandLabelSelectorare 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
MinCleanupTimeoutat 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))whereistarts at 1 produces 'b', 'c', 'd' rather than 'a', 'b', 'c'. This works for uniqueness but may be unintentional. Consider using'a'+i-1or 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
sharedEnvimplementsTestEnvbut is not a*TestEnvPrebuilt, the type assertion silently succeeds as false and cleanup is skipped. While currently onlyTestEnvPrebuiltis 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 whenSKIP_K8S_INTEGRATION_TESTSis set.When the env var is set,
setupErrremains nil butsharedEnvis nil, soGetSharedEnvfails with "Shared test environment is not initialized". This works but the error message doesn't indicate the skip was intentional. Consider usingt.Skip()or setting a descriptivesetupErr.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 fromBuiltinVariables
BuiltinVariablesreturns the package-levelbuiltinVariablesslice 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 insideManifestItemsfrommanifest.refsas well
validateTemplateVariablescurrently validates inline/single-ref manifests viaresource.Manifest, but skipsresource.ManifestItems, which hold the content formanifest.refsarrays. 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
ManifestItemstoo: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-refsresources.Also applies to: 461-464
internal/config_loader/loader_test.go (1)
717-853: Consider adding a test for path traversal rejection in file referencesYou now guard against path traversal in
SchemaValidator.resolvePath, but the tests only cover “file exists/does not exist/is directory” under a normaltemplates/…subtree. It would be useful to add a case wherebuildRef/manifest.refuses 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
⛔ Files ignored due to path filters (1)
go.sumis 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
.PHONYtargets 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 cleanbefore 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 helpfulThe
var _ TestEnv = (*TestEnvPrebuilt)(nil)pattern is a good, compile-time way to ensureTestEnvPrebuiltcontinues to satisfy theTestEnvinterface as things evolve. No issues here.test/integration/testutil/container.go (8)
9-10: LGTM: Import additions support new CLI cleanup functionality.The
os/execandstringsimports are required for the newforceCleanupContainerandCleanupLeakedContainersfunctions 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
SharedContainertype andStartSharedContainerfunction provide a clean pattern for sharing containers across tests inTestMain. Key design choices:
- No automatic cleanup registration (caller must invoke
Cleanup()explicitly) — correct for TestMain usageMaxRetriesdefaults to 3 (vs. 1 for regular containers) — reasonable for shared infrastructureprintlnfor logging — acceptable sincetesting.Tisn't available in TestMain context
497-515: LGTM: TestMain-compatible force cleanup helper.The
forceCleanupContainerNoTestfunction appropriately mirrorsforceCleanupContainerbut usesprintlninstead oft.Logf, making it suitable for cleanup inTestMainwheretesting.Tisn'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 usest.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 beforeprovider.DaemonHost(ctx)has been properly addressed. The context is now correctly managed withdefer 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
MinVersionin the TLS config. However, since this is test code connecting to a local envtest container withInsecureSkipVerify: truealready set (acknowledged by thenolintcomment), the security implications are negligible. The existingnolint:goseccomment appropriately justifies the trade-off.
88-92: Clear documentation for no-op Cleanup method.The comment clearly explains why
Cleanupis a no-op for shared containers and directs users toCleanupSharedEnv. This is a good pattern for shared test resources.
94-177: Robust error handling with proper cleanup on failures.
setupSharedTestEnvcorrectly cleans up the container on failure paths (API server readiness check, client creation). Returning errors instead of panicking allows graceful handling inTestMain.
204-208: VerifyisAlreadyExistsErrorhelper is defined.The function
isAlreadyExistsErroris 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
requiredandsourceattributes. 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 solidThe operator enum, helper functions (
IsValidOperator,OperatorStrings),EvaluationContext, andEvaluationErrorare 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 robustDynamic env construction,
EvaluateSafeerror normalization, andConditionToCELoperator handling are consistent and well-documented. The behavior around missing fields (errors surfaced viaCELResult) 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 theClientinterface line up cleanly with the implementation inclient.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 safeThe structural validators plus
validateFileReferences/loadFileReferencesform a clear pipeline, andresolvePath’s use offilepath.Abs+filepath.Relto reject paths that escapebaseDiris 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
doRequestunconditionally wraps the parent context withcontext.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.WithTimeoutwith a zero or negative duration immediately returns a context with an already-expired deadline (triggeringcontext.DeadlineExceededright away). If a caller supplies a customClientConfigwithTimeout == 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_loaderandinternal/k8s_clientexport aDiscoveryConfigtype 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.,
ResourceDiscoveryConfigin this package orK8sDiscoveryConfigin k8s_client) or consolidating if the types can be unified.
| Validation errors are descriptive: | ||
| ``` | ||
| spec.params[0].name is required | ||
| spec.preconditions[1].apiCall.method must be one of: GET, POST, PUT, PATCH, DELETE | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
|
[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 DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
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
✏️ Tip: You can customize this high-level summary in your review settings.