Skip to content

OTA-1975: add products service to serve products.json if present in graph-data - #1072

Open
ankitathomas wants to merge 1 commit into
openshift:masterfrom
ankitathomas:products-data
Open

OTA-1975: add products service to serve products.json if present in graph-data#1072
ankitathomas wants to merge 1 commit into
openshift:masterfrom
ankitathomas:products-data

Conversation

@ankitathomas

@ankitathomas ankitathomas commented Jul 9, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Added a /products endpoint to serve product lifecycle data, with optional name-based filtering.
    • Added runtime configuration for enabling product data, API endpoint, polling interval, and request timeout (disabled by default).
  • Bug Fixes

    • Extended end-to-end retry timing to better accommodate slower startup or service responses.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 9, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 9, 2026

Copy link
Copy Markdown

@ankitathomas: This pull request references OTA-1975 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Walkthrough

Product lifecycle settings and a conditional public products.json endpoint were added to graph-builder. The endpoint supports optional case-insensitive name filtering and JSON error responses. The e2e backoff helper now allows up to 90 retry attempts instead of 60.

Changes

Product serving

Layer / File(s) Summary
Product settings and products handler
graph-builder/src/config/settings.rs, graph-builder/Cargo.toml, graph-builder/src/graph.rs
Adds product enablement and timing settings, URL decoding support, and a handler that reads, filters, and returns products.json with error responses for failures.
Conditional route and validation
graph-builder/src/main.rs
Registers the products route when enabled and expands the basic product data and filtering test setup.

E2E retry behavior

Layer / File(s) Summary
Extended e2e backoff
hack/e2e.sh
Raises the backoff helper’s maximum attempts from 60 to 90.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ActixServer
  participant serve_products
  participant FileSystem
  Client->>ActixServer: GET /products with optional name query
  ActixServer->>serve_products: Dispatch request
  serve_products->>FileSystem: Read products.json
  FileSystem-->>serve_products: JSON content or read error
  serve_products-->>Client: Filtered or unfiltered JSON, or 500 error response
Loading

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error main.rs logs the full AppSettings via Debug; the new product_api_url field is included and may reveal internal hostnames at startup. Redact or remove the full settings dump, or log only non-sensitive fields so endpoint URLs and secret paths are not emitted.
Test Structure And Quality ⚠️ Warning serve_products_basic mixes JSON validation and filtering logic, never exercises the HTTP handler, and uses bare asserts without failure messages. Split it into focused tests that call serve_products via a fixture-backed path, and add contextual messages to assertions.
✅ Passed checks (13 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a products service to serve products.json from graph-data.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed No Ginkgo tests or dynamic titles were added; the new Rust test functions use static names like serve_products_basic and check_liveness_readiness.
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the only test change is a Rust #[test], and no MicroShift-unsupported OpenShift APIs/features appear in new test code.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the new Rust unit test is local-file logic with no multi-node or HA assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed No scheduling constraints, affinities, nodeSelectors, replica logic, or topology checks were added; changes are only config, HTTP handler, routing, tests, and backoff.
Ote Binary Stdout Contract ✅ Passed No process-level stdout writes were added; the only println! is inside a #[test], and main() logs via env_logger (stderr by default).
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the new Rust test is local-only JSON logic and the patch shows no IPv4-only or external connectivity assumptions.
No-Weak-Crypto ✅ Passed No weak crypto, custom crypto, or secret/token comparisons were added; the diff only adds settings, routing, and URL decoding.
Container-Privileges ✅ Passed PASS: The PR changes only Rust/Cargo/shell files; the diff contains no privileged, hostPID/hostNetwork, allowPrivilegeEscalation, or SYS_ADMIN settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci

openshift-ci Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: ankitathomas
Once this PR has been reviewed and has the lgtm label, please assign fao89 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

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (4)
products-service/Cargo.toml (2)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

structopt is in maintenance mode; consider clap's derive API.

structopt's features were absorbed into clap v3+ derive; structopt itself receives no new features. Since this is a new crate, using clap directly would avoid starting on a maintenance-mode dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@products-service/Cargo.toml` at line 15, The dependency list is using
structopt, which is now maintenance-only; replace it with clap and switch the
argument parsing code to clap’s derive API. Update the relevant CLI types and
derive imports that currently rely on structopt so the crate starts on clap
directly rather than introducing a deprecated dependency.

8-16: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Pin dependency versions exactly for reproducible builds.

All dependencies use caret ranges (^4.4.1, ^0.10, ^0.4.20, etc.), allowing automatic minor/patch upgrades on cargo update. As per path instructions, "Pin exact versions; verify hashes where supported" for dependency manifests.

♻️ Example fix (pin to exact versions)
-  actix-web = "^4.4.1"
+  actix-web = "=4.4.1"
   commons = { path = "../commons" }
-  env_logger = "^0.10"
+  env_logger = "=0.10.0"
-  log = "^0.4.20"
+  log = "=0.4.20"
-  parking_lot = "^0.12"
+  parking_lot = "=0.12.0"
-  prometheus = "0.13"
+  prometheus = "=0.13.0"
-  serde_json = "^1.0.109"
+  serde_json = "=1.0.109"
-  structopt = "^0.3"
+  structopt = "=0.3.26"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@products-service/Cargo.toml` around lines 8 - 16, The Cargo manifest
currently uses caret version ranges for dependencies like actix-web, env_logger,
log, parking_lot, prometheus, serde_json, structopt, and actix-service, which
allows unintentional upgrades. Update the dependency entries in
products-service/Cargo.toml to exact pinned versions so builds are reproducible,
keeping the existing path dependency for commons unchanged and preserving the
same dependency names in the manifest.

Source: Path instructions

products-service/src/config/settings.rs (1)

22-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Default path duplicated with CLI default.

"/var/lib/cincinnati/graph-data/products" appears both here and as the default_value in cli.rs. Since try_merge unconditionally overwrites data_directory from CLI (which itself defaults via structopt), this Default value is effectively unreachable at runtime but still needs to stay in sync. Extract to a shared constant to avoid drift.

♻️ Suggested fix
// in a shared location, e.g. config/mod.rs
pub(crate) const DEFAULT_DATA_DIRECTORY: &str = "/var/lib/cincinnati/graph-data/products";
-            data_directory: PathBuf::from("/var/lib/cincinnati/graph-data/products"),
+            data_directory: PathBuf::from(super::DEFAULT_DATA_DIRECTORY),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@products-service/src/config/settings.rs` around lines 22 - 31, The default
data directory string is duplicated between AppSettings::default and the CLI
default, so move the value to a shared constant and use it in both places. Add a
reusable DEFAULT_DATA_DIRECTORY in a common config location, then update
AppSettings::default and the CLI argument definition in cli.rs to reference that
constant so try_merge and future changes stay in sync.
products-service/src/main.rs (1)

33-47: 🧹 Nitpick | 🔵 Trivial

Products data is loaded once at startup with no refresh mechanism.

If products-lifecycle.json changes on disk (e.g., graph-data update), the service won't pick it up without a restart. Consider a periodic reload similar to graph-builder's refresh pattern, especially since this data is expected to be updated externally via graph-data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@products-service/src/main.rs` around lines 33 - 47, The startup-only load in
main.rs currently caches products-lifecycle.json forever, so updates on disk are
never picked up. Add a periodic refresh/reload path similar to graph-builder’s
refresh pattern: refactor the existing products_file/products_data loading logic
in main to be reusable, then schedule a background reload that re-reads the file
and swaps the in-memory JSON when it changes. Keep the current info!/warn!
behavior in the reload path and ensure the service continues serving the last
valid data if a refresh fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@products-service/src/main.rs`:
- Around line 52-53: The ready flag in AppState is currently dead state because
only the live health check is exposed via /healthz and nothing reads or updates
ready. Either remove the ready RwLock from main/AppState if readiness is not
needed, or implement a real readiness signal tied to products loading and add a
dedicated /readyz route that reads ready; update the handler wiring where the
state is constructed and registered so the symbol AppState reflects only used
health state.

---

Nitpick comments:
In `@products-service/Cargo.toml`:
- Line 15: The dependency list is using structopt, which is now
maintenance-only; replace it with clap and switch the argument parsing code to
clap’s derive API. Update the relevant CLI types and derive imports that
currently rely on structopt so the crate starts on clap directly rather than
introducing a deprecated dependency.
- Around line 8-16: The Cargo manifest currently uses caret version ranges for
dependencies like actix-web, env_logger, log, parking_lot, prometheus,
serde_json, structopt, and actix-service, which allows unintentional upgrades.
Update the dependency entries in products-service/Cargo.toml to exact pinned
versions so builds are reproducible, keeping the existing path dependency for
commons unchanged and preserving the same dependency names in the manifest.

In `@products-service/src/config/settings.rs`:
- Around line 22-31: The default data directory string is duplicated between
AppSettings::default and the CLI default, so move the value to a shared constant
and use it in both places. Add a reusable DEFAULT_DATA_DIRECTORY in a common
config location, then update AppSettings::default and the CLI argument
definition in cli.rs to reference that constant so try_merge and future changes
stay in sync.

In `@products-service/src/main.rs`:
- Around line 33-47: The startup-only load in main.rs currently caches
products-lifecycle.json forever, so updates on disk are never picked up. Add a
periodic refresh/reload path similar to graph-builder’s refresh pattern:
refactor the existing products_file/products_data loading logic in main to be
reusable, then schedule a background reload that re-reads the file and swaps the
in-memory JSON when it changes. Keep the current info!/warn! behavior in the
reload path and ensure the service continues serving the last valid data if a
refresh fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ca59121e-0eb9-4b0b-b76c-5d9959897e2a

📥 Commits

Reviewing files that changed from the base of the PR and between 6121cea and 19f4649.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • dist/Dockerfile.deploy/Dockerfile is excluded by !**/dist/**, !dist/**
📒 Files selected for processing (6)
  • Cargo.toml
  • products-service/Cargo.toml
  • products-service/src/config/cli.rs
  • products-service/src/config/mod.rs
  • products-service/src/config/settings.rs
  • products-service/src/main.rs

Comment thread products-service/src/main.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
products-service/src/main.rs (1)

40-53: 🗄️ Data Integrity & Integration | 🟠 Major

Make readiness reflect successful product initialization.

ready is set to true even when reading the product file fails and {} is served, so /readyz returns 200 while required data is unavailable. Set readiness from the load result, and validate the JSON before marking the service ready if malformed files are possible.

This is the same unresolved readiness concern from the previous review; the route now exists, but the state remains unconditional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@products-service/src/main.rs` around lines 40 - 53, Update the product
initialization flow in main and the shared ready state so readiness is derived
from successful product-file loading rather than unconditionally set to true.
Track whether the read succeeded, validate the loaded JSON before marking the
service ready, and keep ready false when loading or parsing fails so /readyz
reports the unavailable-data state.
🧹 Nitpick comments (1)
products-service/src/build.rs (1)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consume or remove the generated build metadata.

built::write_built_file() generates metadata, but the provided products-service/src/main.rs never includes or references it. Either expose/use the generated constants or remove this build script and dependency to avoid unnecessary build complexity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@products-service/src/build.rs` around lines 1 - 3, Update the build metadata
flow around main and the build script: either include and use the file generated
by built::write_built_file in products-service/src/main.rs, or remove the build
script and built dependency entirely. Ensure no unused generation remains and
preserve the service’s existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@products-service/src/main.rs`:
- Around line 33-35: Align all product-file defaults with the intended location:
in products-service/src/main.rs lines 33-35, load products.json directly from
the configured graph-data directory; in products-service/src/config/settings.rs
lines 14-29, update the default directory, filename, and documentation; and in
products-service/src/config/cli.rs lines 15-20, mirror the corrected default and
help text.

---

Duplicate comments:
In `@products-service/src/main.rs`:
- Around line 40-53: Update the product initialization flow in main and the
shared ready state so readiness is derived from successful product-file loading
rather than unconditionally set to true. Track whether the read succeeded,
validate the loaded JSON before marking the service ready, and keep ready false
when loading or parsing fails so /readyz reports the unavailable-data state.

---

Nitpick comments:
In `@products-service/src/build.rs`:
- Around line 1-3: Update the build metadata flow around main and the build
script: either include and use the file generated by built::write_built_file in
products-service/src/main.rs, or remove the build script and built dependency
entirely. Ensure no unused generation remains and preserve the service’s
existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8ee29591-044b-48b6-9ff6-8922e56e3f85

📥 Commits

Reviewing files that changed from the base of the PR and between 6121cea and 5a62824.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • dist/Dockerfile.deploy/Dockerfile is excluded by !**/dist/**, !dist/**
📒 Files selected for processing (7)
  • Cargo.toml
  • products-service/Cargo.toml
  • products-service/src/build.rs
  • products-service/src/config/cli.rs
  • products-service/src/config/mod.rs
  • products-service/src/config/settings.rs
  • products-service/src/main.rs

Comment thread products-service/src/main.rs Outdated
Comment on lines +33 to +35
// Load products data on startup
let products_file = settings.data_directory.join("products-lifecycle.json");
let products_data = match fs::read_to_string(&products_file) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align all layers with the intended product-file path.

The effective default currently resolves to /var/lib/cincinnati/graph-data/products/products-lifecycle.json, while the PR objective specifies products.json in graph-data. This causes the service to miss the expected file and serve fallback data.

  • products-service/src/main.rs#L33-L35: load products.json from the intended directory.
  • products-service/src/config/settings.rs#L14-L29: update the default directory and filename documentation.
  • products-service/src/config/cli.rs#L15-L20: mirror the corrected default and help text.
📍 Affects 3 files
  • products-service/src/main.rs#L33-L35 (this comment)
  • products-service/src/config/settings.rs#L14-L29
  • products-service/src/config/cli.rs#L15-L20
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@products-service/src/main.rs` around lines 33 - 35, Align all product-file
defaults with the intended location: in products-service/src/main.rs lines
33-35, load products.json directly from the configured graph-data directory; in
products-service/src/config/settings.rs lines 14-29, update the default
directory, filename, and documentation; and in
products-service/src/config/cli.rs lines 15-20, mirror the corrected default and
help text.

@ankitathomas ankitathomas changed the title [WIP] OTA-1975: add products service to serve products.json if present in graph-data OTA-1975: add products service to serve products.json if present in graph-data Jul 20, 2026
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 20, 2026
@ankitathomas ankitathomas reopened this Jul 20, 2026
@ankitathomas

Copy link
Copy Markdown
Author

/retest

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@graph-builder/src/graph.rs`:
- Around line 153-157: Update the products file-opening error handling around
NamedFile::open to inspect the underlying I/O error kind: map
ErrorKind::NotFound to GraphError::DoesNotExist so GET /products returns 404,
while retaining GraphError::FileOpenError for permission and other failures.
Avoid consuming the result before extracting the error details.

In `@graph-builder/src/main.rs`:
- Around line 300-310: Update the serve_products_basic test to create the
fixture at the handler’s expected /var/lib/cincinnati/graph-data/products.json
location, initialize the Actix application with the products route, issue a GET
request, and assert the expected status and response body, including the
missing-file error case.

In `@hack/e2e.sh`:
- Line 42: Update the retry loop using max_attempts so its termination condition
enforces exactly 90 total executions, changing the boundary comparison to -ge
while preserving the existing backoff behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 985f121b-8204-40db-9bd5-4125c6d4975d

📥 Commits

Reviewing files that changed from the base of the PR and between 90ca277 and 9a06a3c.

📒 Files selected for processing (4)
  • graph-builder/src/config/settings.rs
  • graph-builder/src/graph.rs
  • graph-builder/src/main.rs
  • hack/e2e.sh

Comment thread graph-builder/src/graph.rs Outdated
Comment on lines +153 to +157
if f.is_err() {
return Err(GraphError::FileOpenError(format!(
"unable to open products.json: {}",
f.unwrap_err()
)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return 404 when products.json is absent.

product_enabled defaults to false, so a missing products file is an expected state. Mapping every NamedFile::open failure to GraphError::FileOpenError makes GET /products return HTTP 500 instead of 404; preserve 500 for permission and other I/O failures, but map ErrorKind::NotFound to GraphError::DoesNotExist.

Proposed fix
-    let f = NamedFile::open(products_path);
-    if f.is_err() {
-        return Err(GraphError::FileOpenError(format!(
-            "unable to open products.json: {}",
-            f.unwrap_err()
-        )));
-    }
-    Ok(f.unwrap())
+    match NamedFile::open(products_path) {
+        Ok(file) => Ok(file),
+        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
+            Err(GraphError::DoesNotExist("products.json".to_string()))
+        }
+        Err(err) => Err(GraphError::FileOpenError(format!(
+            "unable to open products.json: {}",
+            err
+        ))),
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if f.is_err() {
return Err(GraphError::FileOpenError(format!(
"unable to open products.json: {}",
f.unwrap_err()
)));
match NamedFile::open(products_path) {
Ok(file) => Ok(file),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
Err(GraphError::DoesNotExist("products.json".to_string()))
}
Err(err) => Err(GraphError::FileOpenError(format!(
"unable to open products.json: {}",
err
))),
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/graph.rs` around lines 153 - 157, Update the products
file-opening error handling around NamedFile::open to inspect the underlying I/O
error kind: map ErrorKind::NotFound to GraphError::DoesNotExist so GET /products
returns 404, while retaining GraphError::FileOpenError for permission and other
failures. Avoid consuming the result before extracting the error details.

Comment thread graph-builder/src/main.rs
Comment on lines +300 to +310
#[test]
fn serve_products_basic() -> Fallible<()> {
use actix_web::test;

// Create test products.json file
let test_data = r#"{"products":[]}"#;
let temp_dir = std::env::temp_dir();
let products_path = temp_dir.join("test_products.json");
std::fs::write(&products_path, test_data)?;

Ok(())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make serve_products_basic exercise the endpoint.

This test only writes /tmp/test_products.json and returns Ok(()); it never invokes the route, and the fixture path does not match the handler’s /var/lib/cincinnati/graph-data/products.json path. It will pass even if the endpoint is broken. Initialize the Actix app, issue a GET request, and assert the response status and body (including the missing-file case).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/main.rs` around lines 300 - 310, Update the
serve_products_basic test to create the fixture at the handler’s expected
/var/lib/cincinnati/graph-data/products.json location, initialize the Actix
application with the products route, issue a GET request, and assert the
expected status and response body, including the missing-file error case.

Comment thread hack/e2e.sh
Signed-off-by: Ankita Thomas <ankithom@redhat.com>
Co-authored-by: Claude

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
graph-builder/src/main.rs (1)

305-358: 🎯 Functional Correctness | 🟡 Minor | 🏗️ Heavy lift

Test still doesn't exercise serve_products.

The test writes its fixture to a tempdir instead of the handler's hardcoded /var/lib/cincinnati/graph-data/products.json, builds a TestRequest/mock_state that are never passed to graph::serve_products, and instead re-implements the retain/filter logic inline to assert against. It will pass even if the real handler is broken. This mirrors the previously raised concern; it will only be resolvable once the products path is injectable (see the related comment on the hardcoded path in graph.rs).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/main.rs` around lines 305 - 358, Update
serve_products_basic and the production serve_products path handling so the
products file location is injectable, then configure the test to use its
temporary products_path. Pass the constructed TestRequest and mock_state into
graph::serve_products, and assert on the actual handler response; remove the
standalone JSON parsing and duplicated filtering logic so the test fails when
serve_products is broken.
🧹 Nitpick comments (4)
graph-builder/Cargo.toml (1)

36-36: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin exact dependency version.

urlencoding = "^2.1" uses a caret range rather than an exact pin. Also worth questioning necessity: actix-web's web::Query extractor (backed by serde_urlencoded) already decodes query parameters, which could remove the need for this new dependency and the manual parsing in graph.rs — see the companion comment on parse_name_filter.

As per path instructions, "Pin exact versions; verify hashes where supported" and "New deps: justify need, check license compatibility".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/Cargo.toml` at line 36, Pin the urlencoding dependency to an
exact version instead of the caret range in Cargo.toml, and verify its lockfile
hash where supported. Before retaining it, assess whether parse_name_filter and
related manual decoding in graph.rs can use actix-web’s
web::Query/serde_urlencoded path; remove the dependency and parsing code if that
existing mechanism fully covers the required behavior.

Source: Path instructions

graph-builder/src/graph.rs (3)

146-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded products path ignores State/app_data.

Unlike graph_data (lines 130-142), which derives its path from app_data.secondary_metadata, serve_products hardcodes /var/lib/cincinnati/graph-data/products.json and leaves _app_data unused. Consider deriving the products path from shared state/config for consistency and testability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/graph.rs` around lines 146 - 151, Update serve_products to
use the provided app_data State, deriving the products.json path from the same
shared configuration source as graph_data (app_data.secondary_metadata) instead
of the hardcoded absolute path. Remove the unused parameter naming and preserve
the existing product-serving behavior after resolving the configured path.

157-163: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Hand-built JSON error bodies can produce invalid JSON.

The error responses interpolate e (an io::Error/serde_json::Error Display) directly into a raw JSON-looking string literal without escaping. If the error message ever contains " or \, the resulting body is malformed JSON despite the application/json content type. Build these with serde_json::json!/Value (or a small error struct + Responder) instead of manual string formatting.

♻️ Example fix
-            return HttpResponse::InternalServerError()
-                .content_type("application/json")
-                .body(format!(
-                    r#"{{"status":"fail","data":{{"code":500,"message":"unable to open products.json: {}"}}}}"#,
-                    e
-                ));
+            return HttpResponse::InternalServerError().json(serde_json::json!({
+                "status": "fail",
+                "data": {"code": 500, "message": format!("unable to open products.json: {}", e)}
+            }));

Also applies to: 183-187, 210-214

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/graph.rs` around lines 157 - 163, Replace the hand-built
JSON error bodies in the affected error-response branches with
serde_json::json!/Value or an equivalent serializable response structure,
ensuring interpolated errors such as e are JSON-escaped before returning them as
application/json. Update all corresponding branches around the products.json and
other parsing failures, while preserving their existing status, code, and
message content.

166-168: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Manual query parsing is fragile and reinvents existing framework support.

parse_name_filter splits on '&'/'=' and requires exactly two parts, so a value containing = (e.g. ?name=a=b) fails to match and is silently dropped instead of being treated as name's value. It also only percent-decodes via urlencoding::decode, so a +-encoded space (form-encoding convention) is not converted to a space, unlike serde_urlencoded used by actix's web::Query. Consider actix_web::web::Query<HashMap<String, String>> (or a typed struct) as the extractor, which handles decoding correctly and removes the need for the manual parser and the urlencoding dependency.

Also applies to: 222-232

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/graph.rs` around lines 166 - 168, Replace the manual
parse_name_filter flow around the request handler with actix_web::web::Query
extraction into a HashMap or typed query struct, preserving the name filter
behavior while correctly handling values containing '=' and '+'-encoded spaces.
Remove the now-unused parse_name_filter implementation and urlencoding
dependency, and update the handler signature and imports accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@graph-builder/src/graph.rs`:
- Line 154: Update the file-reading flow in the async handler serve_products to
use non-blocking Tokio file I/O with tokio::fs::read_to_string(...).await,
preserving the existing match-based success and error handling.
- Around line 154-164: Update the read_to_string error handling in the graph
handler to distinguish std::io::ErrorKind::NotFound from other failures: return
an HTTP 404 for a missing products.json, while preserving the existing 500
response for other I/O errors and the successful content path.

---

Duplicate comments:
In `@graph-builder/src/main.rs`:
- Around line 305-358: Update serve_products_basic and the production
serve_products path handling so the products file location is injectable, then
configure the test to use its temporary products_path. Pass the constructed
TestRequest and mock_state into graph::serve_products, and assert on the actual
handler response; remove the standalone JSON parsing and duplicated filtering
logic so the test fails when serve_products is broken.

---

Nitpick comments:
In `@graph-builder/Cargo.toml`:
- Line 36: Pin the urlencoding dependency to an exact version instead of the
caret range in Cargo.toml, and verify its lockfile hash where supported. Before
retaining it, assess whether parse_name_filter and related manual decoding in
graph.rs can use actix-web’s web::Query/serde_urlencoded path; remove the
dependency and parsing code if that existing mechanism fully covers the required
behavior.

In `@graph-builder/src/graph.rs`:
- Around line 146-151: Update serve_products to use the provided app_data State,
deriving the products.json path from the same shared configuration source as
graph_data (app_data.secondary_metadata) instead of the hardcoded absolute path.
Remove the unused parameter naming and preserve the existing product-serving
behavior after resolving the configured path.
- Around line 157-163: Replace the hand-built JSON error bodies in the affected
error-response branches with serde_json::json!/Value or an equivalent
serializable response structure, ensuring interpolated errors such as e are
JSON-escaped before returning them as application/json. Update all corresponding
branches around the products.json and other parsing failures, while preserving
their existing status, code, and message content.
- Around line 166-168: Replace the manual parse_name_filter flow around the
request handler with actix_web::web::Query extraction into a HashMap or typed
query struct, preserving the name filter behavior while correctly handling
values containing '=' and '+'-encoded spaces. Remove the now-unused
parse_name_filter implementation and urlencoding dependency, and update the
handler signature and imports accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 40a614c3-6232-49fb-945e-8d4ce3b32e73

📥 Commits

Reviewing files that changed from the base of the PR and between 9a06a3c and 913ffc7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • graph-builder/Cargo.toml
  • graph-builder/src/config/settings.rs
  • graph-builder/src/graph.rs
  • graph-builder/src/main.rs
  • hack/e2e.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • hack/e2e.sh
  • graph-builder/src/config/settings.rs

let products_path = std::path::Path::new("/var/lib/cincinnati/graph-data/products.json");

// Read the file content
let content = match std::fs::read_to_string(products_path) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Blocking file I/O inside an async handler.

std::fs::read_to_string performs a synchronous, blocking syscall directly inside the async fn serve_products, tying up an actix worker thread while reading the file. Prefer tokio::fs::read_to_string(...).await or wrap the read in actix_web::web::block to avoid blocking the executor under load or slow storage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/graph.rs` at line 154, Update the file-reading flow in the
async handler serve_products to use non-blocking Tokio file I/O with
tokio::fs::read_to_string(...).await, preserving the existing match-based
success and error handling.

Comment on lines +154 to +164
let content = match std::fs::read_to_string(products_path) {
Ok(c) => c,
Err(e) => {
return HttpResponse::InternalServerError()
.content_type("application/json")
.body(format!(
r#"{{"status":"fail","data":{{"code":500,"message":"unable to open products.json: {}"}}}}"#,
e
));
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing products.json still returns 500 instead of 404.

Every read_to_string failure — including a missing file, which is an expected state since product_enabled may be true before graph-data population — maps to InternalServerError. This mirrors a previously flagged issue (originally against the NamedFile::open-based version of this handler): distinguish ErrorKind::NotFound and return 404 instead of 500.

🐛 Proposed fix
     let content = match std::fs::read_to_string(products_path) {
         Ok(c) => c,
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
+            return HttpResponse::NotFound()
+                .content_type("application/json")
+                .body(r#"{"status":"fail","data":{"code":404,"message":"products.json not found"}}"#);
+        }
         Err(e) => {
             return HttpResponse::InternalServerError()
                 .content_type("application/json")
                 .body(format!(
                     r#"{{"status":"fail","data":{{"code":500,"message":"unable to open products.json: {}"}}}}"#,
                     e
                 ));
         }
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@graph-builder/src/graph.rs` around lines 154 - 164, Update the read_to_string
error handling in the graph handler to distinguish std::io::ErrorKind::NotFound
from other failures: return an HTTP 404 for a missing products.json, while
preserving the existing 500 response for other I/O errors and the successful
content path.

@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@ankitathomas: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/cargo-test 913ffc7 link true /test cargo-test
ci/prow/rustfmt 913ffc7 link true /test rustfmt

Full PR test history. Your PR dashboard.

Details

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. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants