Skip to content

Comments

HYPERFLEET-652 - feat: dry-run mode#49

Merged
ciaranRoche merged 1 commit intoopenshift-hyperfleet:mainfrom
rh-amarin:new-dryrun
Feb 18, 2026
Merged

HYPERFLEET-652 - feat: dry-run mode#49
ciaranRoche merged 1 commit intoopenshift-hyperfleet:mainfrom
rh-amarin:new-dryrun

Conversation

@rh-amarin
Copy link
Contributor

@rh-amarin rh-amarin commented Feb 17, 2026

https://issues.redhat.com/browse/HYPERFLEET-652

  • Adds a dry-run mode that simulates the full adapter execution pipeline (params → preconditions → resources → post-actions) locally, without connecting to any real infrastructure (broker, Kubernetes, Maestro, or HyperFleet API)
    • Introduces mock clients (DryrunAPIClient, DryrunTransportClient) that record all operations in-memory and support configurable responses via JSON files
    • Produces a detailed execution trace (text or JSON) showing what the adapter would do for a given CloudEvent, including extracted params, precondition results, resource operations, and post-action calls
    • Refactors cmd/adapter/main.go to extract shared logic (config loading, client creation, executor building) into reusable functions used by both serve and dry-run modes
    • Fixes resource identity (Kind, Namespace, Name) to be populated from the rendered manifest before transport apply, so the trace can report accurate resource details

It can be tested with the test files at test/testdata/dryrun

go run ./cmd/adapter/ serve \
    --config ./test/testdata/dryrun/adapter-config.yaml \
    --task-config ./test/testdata/dryrun/task-config.yaml \
    --dry-run-event ./test/testdata/dryrun/event.json \
    --dry-run-api-responses ./test/testdata/dryrun/dryrun-api-responses.json \
    --dry-run-discovery ./test/testdata/dryrun/dryrun-discovery.json \
    --dry-run-verbose

Test plan

  • Verify dry-run with minimal flags: --dry-run-event event.json produces a text trace
  • Verify --dry-run-api-responses correctly matches requests by method and URL regex
  • Verify --dry-run-discovery overrides replace applied resources for discovery phase
  • Verify --dry-run-verbose includes rendered manifests and API request/response bodies
  • Verify --dry-run-output json produces valid structured JSON output
  • Verify normal serve mode is not affected by the refactoring
  • Run existing unit and integration tests: make test && make test-integration

Summary by CodeRabbit

  • New Features

    • Local dry-run mode for running adapter workflows with mock API and transport clients
    • CLI flags to control dry-run event, API responses, discovery overrides, verbosity, and output format
    • Execution trace output (text or JSON) with optional verbose API/transport details
    • Dry-run input testdata for events, API responses, discovery, and configs
  • Documentation

    • Expanded README with dry-run usage, flags table, input/output examples, and testdata references
    • Minor formatting and config-path clarifications
  • Bug Fixes / Improvements

    • Resource identity now reported in execution results; serve/run flows unified for consistency
  • Tests

    • Extensive unit tests for dry-run loaders, mock clients, transport, trace formatting, and concurrency

@openshift-ci openshift-ci bot requested review from crizzo71 and vkareh February 17, 2026 13:21
@openshift-ci
Copy link

openshift-ci bot commented Feb 17, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign vkareh for approval. For more information see the Code Review Process.

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

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

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

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Walkthrough

Adds a local dry-run mode and documentation, refactors adapter startup to extract shared config/client/runner builders, and implements an internal dryrun package with: a mock HyperFleet API client, an in-memory transport client with operation recording and discovery overrides, loaders for CloudEvents and mock API responses, and an execution trace subsystem (text/JSON). Adds tests and testdata for dry-run inputs/outputs. Adjusts executor to capture resource identity fields for trace reporting and reuses the builder/executor pattern for both serve and dry-run paths.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Main as cmd/adapter/main.go
    participant Config as Config Loader
    participant Event as Dryrun Event Loader
    participant API as Dryrun API Client
    participant Transport as Dryrun Transport Client
    participant Executor as Executor
    participant Trace as Trace Formatter
    participant Output as File/Stdout

    User->>Main: invoke with --dry-run-event / --dry-run-api-responses / --dry-run-discovery
    Main->>Config: loadConfig(...)
    Config-->>Main: config
    Main->>Event: LoadCloudEvent(path)
    Event-->>Main: CloudEvent
    Main->>API: NewDryrunAPIClient(responses)
    API-->>Main: dry-run API client
    Main->>Transport: NewDryrunTransportClient(with overrides)
    Transport-->>Main: dry-run transport client
    Main->>Executor: buildExecutor(config, API, Transport)
    Executor-->>Main: executor ready
    Main->>Executor: Execute(event)
    Executor->>API: Do(request)
    API-->>Executor: mock response
    Executor->>Transport: ApplyResource(manifest)
    Transport-->>Executor: ApplyResult + record
    Executor->>Transport: DiscoverResources(gvk)
    Transport-->>Executor: discovered resources
    Executor-->>Main: ExecutionResult
    Main->>Trace: FormatText/FormatJSON(result)
    Trace-->>Main: formatted trace
    Main->>Output: write trace
    Output-->>User: trace file / stdout
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

lgtm, approved

Suggested reviewers

  • yingzhanredhat
  • vkareh
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "HYPERFLEET-652 - feat: dry-run mode" directly describes the main feature addition: a new dry-run mode for the adapter that simulates execution without external infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (1)
internal/dryrun/event_loader_test.go (1)

43-53: Consider adding a test for CloudEvent validation failure.

The tests cover file-not-found and invalid JSON, but there's no test for the evt.Validate() error path (Line 25-27 in event_loader.go). A CloudEvent with valid JSON but missing required fields (e.g., missing specversion or type) would exercise this branch.

💡 Example test case
func TestLoadCloudEvent_InvalidCloudEvent(t *testing.T) {
	t.Run("returns error for valid JSON but invalid CloudEvent", func(t *testing.T) {
		dir := t.TempDir()
		// Valid JSON but missing required CloudEvent fields
		path := writeEventFile(t, dir, "invalid-event.json", `{"id":"test-123"}`)

		_, err := LoadCloudEvent(path)

		require.Error(t, err)
		assert.Contains(t, err.Error(), "invalid CloudEvent")
	})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dryrun/event_loader_test.go` around lines 43 - 53, Add a unit test
that exercises the CloudEvent validation error path in LoadCloudEvent by writing
a file containing syntactically valid JSON that lacks required CloudEvent fields
(e.g., missing "specversion" or "type"), calling LoadCloudEvent on that path,
and asserting an error is returned and its message contains an indication of an
invalid CloudEvent; reference the test helper writeEventFile and the function
under test LoadCloudEvent/evt.Validate to locate where to add the new test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/adapter/main.go`:
- Around line 646-655: The switch over dryRunOutput silently falls back to text
for unknown values; validate the flag and fail fast instead. Update the handling
around the dryRunOutput variable used with trace.FormatJSON() and
trace.FormatText() to explicitly accept only supported values (e.g., "json" and
"text") and return a descriptive error for any other value rather than using the
default branch; alternatively add validation where the flag is parsed and return
fmt.Errorf("invalid --dry-run-output: %q, allowed: json,text") so callers see a
clear failure.

In `@internal/dryrun/dryrun_api_client.go`:
- Around line 3-35: The file fails gofmt; reformat
internal/dryrun/dryrun_api_client.go using gofmt (or goimports) so imports,
struct declarations (e.g., DryrunAPIClient, compiledEndpoint) and all
spacing/indentation adhere to gofmt rules; run `gofmt -w` (or `gofmt -w &&
goimports -w`) on the file and commit the resulting formatting changes to
resolve the linter error.

In `@internal/dryrun/recording_transport_client_test.go`:
- Around line 26-29: The method declarations on testDiscovery (GetNamespace,
GetName, GetLabelSelector, IsSingleResource) have extra spaces before their
opening braces causing gofmt failures; fix by removing the extra spaces so each
signature directly precedes the `{` (e.g., change `func (d *testDiscovery)
GetNamespace() string    {` to `func (d *testDiscovery) GetNamespace() string
{`) or simply run `gofmt -w` on the file to auto-apply the formatting for all
these methods.

In `@internal/dryrun/recording_transport_client.go`:
- Around line 145-180: DiscoverResources in DryrunTransportClient currently
filters resources only by GVK, namespace and name, ignoring label selectors from
manifest.Discovery; update DiscoverResources to apply
discovery.GetLabelSelector() (when non-empty) to each object's labels
(obj.GetLabels()) and skip objects that don't match the selector, ensuring
behavior matches real discovery; use the existing loop in DiscoverResources and
add a label-match check before appending to list.Items so label selectors are
respected.
- Around line 60-75: The ApplyResource method in DryrunTransportClient currently
only calls json.Unmarshal on manifestBytes; change it to mirror
K8sClient/MaestroClient by attempting JSON first and, if that fails, falling
back to YAML parsing (e.g., use the same yaml.Unmarshal/sigs.k8s.io/yaml logic
those clients use) to populate obj.Object; update the error handling to record
the parse error only after both JSON and YAML attempts fail and ensure
TransportRecord.Manifest still stores the original manifestBytes.

In `@internal/dryrun/trace.go`:
- Around line 88-262: In ExecutionTrace.FormatText update the verbose manifest
lookup that iterates t.Transport.Records (the block that currently matches on
tr.Operation=="apply" && tr.Name==rr.ResourceName && tr.Namespace==rr.Namespace)
to also match the resource Kind (e.g., compare tr.Kind to rr.Kind, using
strings.EqualFold for case-insensitive comparison) so manifests are only shown
for the exact Kind/Namespace/Name triple and avoid collisions across kinds; keep
the rest of the logic (verbose rendering via tr.Manifest and break) unchanged.

In `@README.md`:
- Around line 207-212: Change the misspelled word "overriden" to "overridden" in
the README text describing the Adapter configuration (the line that mentions
"Configure topic/subscription in the `adapter-config.yaml` but can be overriden
with env variables or cli params"); update that single word so the sentence
reads "...but can be overridden with env variables or cli params".

In `@test/testdata/dryrun/dryrun-discovery.json`:
- Around line 30-37: The ConfigMap metadata contains a non-standard field
"ciaran": "roche" under the metadata object for the resource named
"abc123-symbol222"; remove this invalid metadata field or move it into a
standard metadata subsection such as metadata.annotations (e.g.,
metadata.annotations["ciaran"]="roche") if the intent is to preserve it for
testing; update any test documentation or comments to indicate it's an
intentional edge-case if kept.

In `@test/testdata/dryrun/task-config.yaml`:
- Around line 244-248: The apiCall for the "update-status" task is using the
singular path "/api/hyperfleet/v1/clusters/{{ .clusterId }}/status" which won't
match the mock API (the mock checks for "/statuses"); change the apiCall.url
value for the "update-status" task to use the plural
"/api/hyperfleet/v1/clusters/{{ .clusterId }}/statuses" and then update any
corresponding mock response pattern in the dryrun-api-responses.json file so the
mock handler (which looks for paths ending with "/statuses") will match.

---

Nitpick comments:
In `@internal/dryrun/event_loader_test.go`:
- Around line 43-53: Add a unit test that exercises the CloudEvent validation
error path in LoadCloudEvent by writing a file containing syntactically valid
JSON that lacks required CloudEvent fields (e.g., missing "specversion" or
"type"), calling LoadCloudEvent on that path, and asserting an error is returned
and its message contains an indication of an invalid CloudEvent; reference the
test helper writeEventFile and the function under test
LoadCloudEvent/evt.Validate to locate where to add the new test.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
internal/dryrun/dryrun_responses.go (1)

45-52: Validate urlPattern regex upfront to fail fast on invalid patterns.
Catching regex compile errors here prevents later runtime surprises in matching logic.

♻️ Proposed change
 import (
 	"encoding/json"
 	"fmt"
 	"os"
+	"regexp"
 )
@@
 	for i, ep := range mrf.Responses {
 		if len(ep.Responses) == 0 {
 			return nil, fmt.Errorf("dryrun responses file %q: endpoint %d has no responses defined", path, i)
 		}
 		if ep.Match.URLPattern == "" {
 			return nil, fmt.Errorf("dryrun responses file %q: endpoint %d has empty urlPattern", path, i)
 		}
+		if _, err := regexp.Compile(ep.Match.URLPattern); err != nil {
+			return nil, fmt.Errorf("dryrun responses file %q: endpoint %d has invalid urlPattern: %w", path, i, err)
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dryrun/dryrun_responses.go` around lines 45 - 52, Loop over
mrf.Responses in the existing validation loop and validate each
ep.Match.URLPattern by attempting to compile it (e.g., using regexp.Compile); if
compilation fails return a descriptive error including the pattern and the regex
error so the function fails fast on invalid patterns. Keep this check adjacent
to the existing emptiness check for ep.Match.URLPattern in the same loop that
iterates mrf.Responses so malformed regexes are caught early.
test/testdata/dryrun/task-config.yaml (1)

52-55: Resolve the TODO for the timestamp expression.
If you want, I can help validate a working expression (e.g., an equivalent of now) for this config format.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/testdata/dryrun/task-config.yaml` around lines 52 - 55, The timestamp
field currently hardcodes a format string instead of producing a current
timestamp; locate the task entry with name "timestamp" and replace the
expression value so it invokes the runtime's "now" function (not a quoted format
literal) and formats it as an RFC3339 string; e.g., remove the escaped quotes
and use the config's expression syntax to call now() (or the template engine's
{{ now }} equivalent) with an RFC3339 formatter so the expression returns a
concrete timestamp rather than the literal format string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/adapter/main.go`:
- Around line 171-174: The isDryRun function currently returns true if either
dryRunEvent or dryRunAPIResponses is non-empty, which can trigger dry-run mode
without an event file and later fail when the code expects an event; update
isDryRun (and any callers if needed) so it only considers dryRunEvent (i.e.,
return true only when dryRunEvent is non-empty) and leave dryRunAPIResponses
used only to toggle API-response recording behavior where those code paths check
that an event exists (see references to isDryRun, dryRunEvent, and
dryRunAPIResponses).

In `@internal/dryrun/dryrun_api_client.go`:
- Around line 79-86: nextResponse can panic when compiledEndpoint.ep.resps is
empty; add a guard at the start of the nextResponse method to handle
len(ep.resps) == 0 (instead of computing idx = -1). For example, return a
sensible zero-value DryrunResponse (or an explicit error/marker) and avoid
advancing ep.callIdx when there are no responses; alternatively validate and
reject empty Responses in NewDryrunAPIClient during construction. Update
nextResponse (and/or NewDryrunAPIClient) to check ep.resps length before
indexing and use the symbols nextResponse, compiledEndpoint, ep.resps,
ep.callIdx, DryrunResponse, and NewDryrunAPIClient to locate and fix the code.

In `@test/testdata/dryrun/dryrun-api-responses.json`:
- Around line 39-53: The mock API entry under the "match" object uses the wrong
singular path; update the "urlPattern" value in the JSON object (the entry with
"match":{"method":"PATCH","urlPattern":...}) to use
"/api/hyperfleet/v1/clusters/.*/statuses" (plural) instead of
"/api/hyperfleet/v1/clusters/.*/status" so it matches the HyperFleet API
contract and other tests.

---

Duplicate comments:
In `@cmd/adapter/main.go`:
- Around line 646-655: dryRunOutput currently falls through to text for unknown
values; update validation so only "json" and "text" are allowed and return an
error for anything else. Modify the switch around dryRunOutput (the branch using
trace.FormatJSON() and trace.FormatText()) to explicitly handle "json" and
"text" and have the default case return a descriptive error (e.g. invalid
--dry-run-output value) or pre-validate dryRunOutput before the switch so
callers fail fast on invalid values.

In `@internal/dryrun/recording_transport_client.go`:
- Around line 60-75: In ApplyResource of DryrunTransportClient, replace the
direct json.Unmarshal into obj.Object with YAML-to-JSON conversion so the method
accepts YAML as well as JSON: call sigs.k8s.io/yaml.YAMLToJSON (or yaml.ToJSON)
on manifestBytes and then json.Unmarshal the resulting JSON into obj.Object;
ensure errors from YAML conversion or JSON unmarshal are recorded in
TransportRecord.Error (keep the same Operation/Manifest fields) and returned,
and keep locking and appending to c.Records unchanged.

In `@README.md`:
- Around line 207-214: Replace the misspelled word "overriden" with the correct
spelling "overridden" in the README.md paragraph that describes adapter
configuration (the sentence mentioning "Configure topic/subscription in the
`adapter-config.yaml` but can be overriden with env variables or cli params");
update that sentence so it reads "overridden" and ensure surrounding
punctuation/capitalization remains unchanged.

In `@test/testdata/dryrun/dryrun-discovery.json`:
- Around line 30-34: The JSON includes a non-standard Kubernetes metadata field
"metadata.ciaran"; either remove this custom key or move it under
"metadata.annotations" as a string entry (e.g., "metadata.annotations.ciaran")
so it conforms to Kubernetes metadata schema; update any references to the
previous "metadata.ciaran" key in tests or fixtures to read from
"metadata.annotations" instead.

In `@test/testdata/dryrun/task-config.yaml`:
- Around line 244-248: The apiCall URL for the "update-status" task uses the
singular endpoint "/api/hyperfleet/v1/clusters/{{ .clusterId }}/status" which
does not match the repository's expected plural form; update the URL to
"/api/hyperfleet/v1/clusters/{{ .clusterId }}/statuses" in the "update-status"
apiCall so dry-run mocks and the HyperFleet API use the correct plural endpoint.

---

Nitpick comments:
In `@internal/dryrun/dryrun_responses.go`:
- Around line 45-52: Loop over mrf.Responses in the existing validation loop and
validate each ep.Match.URLPattern by attempting to compile it (e.g., using
regexp.Compile); if compilation fails return a descriptive error including the
pattern and the regex error so the function fails fast on invalid patterns. Keep
this check adjacent to the existing emptiness check for ep.Match.URLPattern in
the same loop that iterates mrf.Responses so malformed regexes are caught early.

In `@test/testdata/dryrun/task-config.yaml`:
- Around line 52-55: The timestamp field currently hardcodes a format string
instead of producing a current timestamp; locate the task entry with name
"timestamp" and replace the expression value so it invokes the runtime's "now"
function (not a quoted format literal) and formats it as an RFC3339 string;
e.g., remove the escaped quotes and use the config's expression syntax to call
now() (or the template engine's {{ now }} equivalent) with an RFC3339 formatter
so the expression returns a concrete timestamp rather than the literal format
string.

Copy link
Contributor

@ciaranRoche ciaranRoche left a comment

Choose a reason for hiding this comment

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

Aside from some of the code rabbit comments, everything looks good to me, tested locally

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
internal/dryrun/trace.go (1)

129-151: Consider defensive bounds checking for API request index.

The apiReqIdx tracking assumes a strict 1:1 correspondence between preconditions/post-actions with APICallMade=true and recorded requests. If this invariant is ever violated (e.g., a request fails before being recorded), the index could become misaligned.

The current implementation does check apiReqIdx < len(t.APIClient.Requests), which prevents panics, but misalignment could show the wrong request for a precondition.

Given this is for dry-run mode with controlled mock clients, this is likely acceptable, but consider adding a comment documenting the assumption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dryrun/trace.go` around lines 129 - 151, The loop that prints API
calls assumes a 1:1 correspondence between entries with APICallMade=true in
result.PreconditionResults (and post-actions) and entries in
t.APIClient.Requests, tracked by apiReqIdx; add a concise comment above the
apiReqIdx := 0 initialization (and/or above the loop) documenting this invariant
and that the existing check apiReqIdx < len(t.APIClient.Requests) is intended to
prevent panics if the invariant is violated, and optionally note that a mismatch
may cause the wrong request to be shown so callers should ensure mock clients
record requests in order (reference symbols: apiReqIdx,
result.PreconditionResults, APICallMade, t.APIClient.Requests, prettyJSON).
internal/dryrun/discovery_overrides_test.go (1)

50-80: Consider adding a test for malformed JSON.

The existing tests cover missing required fields well. For completeness, you might add a test for invalid JSON syntax to verify the parse error path.

💡 Optional test case
func TestLoadDiscoveryOverrides_InvalidJSON(t *testing.T) {
	t.Run("malformed JSON returns parse error", func(t *testing.T) {
		dir := t.TempDir()
		path := writeOverrideFile(t, dir, "overrides.json", `{not valid json}`)

		_, err := LoadDiscoveryOverrides(path)

		require.Error(t, err)
		assert.Contains(t, err.Error(), "failed to parse")
	})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dryrun/discovery_overrides_test.go` around lines 50 - 80, Add a unit
test that verifies LoadDiscoveryOverrides returns an error when the overrides
file contains malformed JSON: create a new test named
TestLoadDiscoveryOverrides_InvalidJSON (or similar) that uses t.TempDir and
writeOverrideFile to write a deliberately invalid JSON string (e.g. "{not valid
json}") to "overrides.json", call LoadDiscoveryOverrides(path), and assert an
error is returned and the error message indicates a parse failure (e.g. contains
"failed to parse" or similar).
cmd/adapter/main.go (1)

572-664: LGTM overall, with one minor observation.

The dry-run implementation is well-structured:

  • Creates logger on stderr to keep stdout clean for trace output
  • Reuses shared config loading and executor building
  • Properly loads and validates all dry-run inputs
  • Outputs trace in the requested format

One observation: when result.Status == executor.StatusFailed, errors are printed to stderr (line 658-660) but the function still returns nil. Consider returning a non-nil error to signal failure to the caller.

💡 Optional: Return error on execution failure
 	if result.Status == executor.StatusFailed {
 		for phase, err := range result.Errors {
 			fmt.Fprintf(os.Stderr, "Error in %s: %v\n", phase, err)
 		}
+		return fmt.Errorf("dry-run execution failed")
 	}

 	return nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/adapter/main.go` around lines 572 - 664, The function runDryRun currently
prints execution errors when result.Status == executor.StatusFailed but still
returns nil; update runDryRun to return a non-nil error in that branch (e.g.,
construct and return an error from the result.Errors map or a summary like
fmt.Errorf("dry-run execution failed: %v", result.Errors)) so callers can detect
failure; modify the block referencing executor.StatusFailed / result.Errors to
both write to stderr and then return the constructed error instead of falling
through to nil.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cmd/adapter/main.go`:
- Around line 171-174: The isDryRun detection is too permissive (returns true if
dryRunAPIResponses is set) which triggers dry-run mode without a required
--dry-run-event; update the isDryRun function so it only returns true when
dryRunEvent is non-empty (i.e., require dryRunEvent rather than either flag), by
changing the boolean expression to check only dryRunEvent != "" so downstream
code expecting a dry-run event won't error unexpectedly; keep references to
isDryRun, dryRunEvent and dryRunAPIResponses so reviewers can verify behavior.

---

Nitpick comments:
In `@cmd/adapter/main.go`:
- Around line 572-664: The function runDryRun currently prints execution errors
when result.Status == executor.StatusFailed but still returns nil; update
runDryRun to return a non-nil error in that branch (e.g., construct and return
an error from the result.Errors map or a summary like fmt.Errorf("dry-run
execution failed: %v", result.Errors)) so callers can detect failure; modify the
block referencing executor.StatusFailed / result.Errors to both write to stderr
and then return the constructed error instead of falling through to nil.

In `@internal/dryrun/discovery_overrides_test.go`:
- Around line 50-80: Add a unit test that verifies LoadDiscoveryOverrides
returns an error when the overrides file contains malformed JSON: create a new
test named TestLoadDiscoveryOverrides_InvalidJSON (or similar) that uses
t.TempDir and writeOverrideFile to write a deliberately invalid JSON string
(e.g. "{not valid json}") to "overrides.json", call
LoadDiscoveryOverrides(path), and assert an error is returned and the error
message indicates a parse failure (e.g. contains "failed to parse" or similar).

In `@internal/dryrun/trace.go`:
- Around line 129-151: The loop that prints API calls assumes a 1:1
correspondence between entries with APICallMade=true in
result.PreconditionResults (and post-actions) and entries in
t.APIClient.Requests, tracked by apiReqIdx; add a concise comment above the
apiReqIdx := 0 initialization (and/or above the loop) documenting this invariant
and that the existing check apiReqIdx < len(t.APIClient.Requests) is intended to
prevent panics if the invariant is violated, and optionally note that a mismatch
may cause the wrong request to be shown so callers should ensure mock clients
record requests in order (reference symbols: apiReqIdx,
result.PreconditionResults, APICallMade, t.APIClient.Requests, prettyJSON).

@ciaranRoche
Copy link
Contributor

/lgtm

@ciaranRoche ciaranRoche merged commit c8b25c6 into openshift-hyperfleet:main Feb 18, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants