Skip to content

EDM-4980: fix inventory crash on mount-only application volumes - #63

Merged
EfratIfergan merged 3 commits into
mainfrom
bugfix/EDM-4980-pydantic-fallback
Aug 11, 2026
Merged

EDM-4980: fix inventory crash on mount-only application volumes#63
EfratIfergan merged 3 commits into
mainfrom
bugfix/EDM-4980-pydantic-fallback

Conversation

@EfratIfergan

@EfratIfergan EfratIfergan commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The flightctl-client SDK's pydantic model for ApplicationVolume declares
image as required and non-nullable, but the RHEM API legitimately returns
mount-only volumes without an image field. This causes a ValidationError
that crashes the entire inventory sync — even if only one device out of many
has a mount-only volume.

This PR adds a fallback mechanism: when _get_data() catches a pydantic
ValidationError, it retries using the SDK's *_without_preload_content
methods that return raw JSON, bypassing pydantic deserialization entirely.

Changes

  • Added _is_pydantic_validation_error() helper to detect pydantic errors
    without importing pydantic
  • Added _get_data_raw() function for paginated raw JSON fetching
  • Added fallback_list_func parameter to _get_data() with automatic
    fallback on ValidationError
  • Updated all 4 callers to pass the appropriate fallback function
  • Hardened _populate_inventory_fleets() to handle raw dicts from fallback
  • Added 11 unit tests covering the fallback mechanism

Test Plan

  • Verify all unit tests pass (python -m pytest tests/unit/ -v)
  • Deploy to a test environment with a device that has a mount-only
    application volume and run ansible-inventory -i inventory.yml --list
  • Verify normal operation (no mount-only volumes) still uses the typed
    SDK path (no warning emitted)
  • Verify the fallback path emits an Ansible warning when triggered

🤖 Generated with Claude Code

Summary

  • Affected area: plugins/inventory/ and tests/unit/plugins/inventory/.
  • Adds a raw JSON fallback for valid mount-only volumes that fail ApplicationVolume pydantic validation because image is missing.
  • Updates _get_data() to accept a fallback_list_func parameter.
  • Adds paginated raw SDK retrieval with continuation-token handling and API error conversion.
  • Updates device and fleet retrieval to use the fallback endpoint.
  • Allows _populate_inventory_fleets() to process raw dictionaries.
  • Preserves existing behavior for non-pydantic errors.
  • Adds 11 unit tests for validation detection, fallback retrieval, pagination, error handling, and dictionary-based fleet processing.
  • Removes unused imports.

API and compatibility

  • No public module API, argument spec, or return-value changes.
  • The internal _get_data() signature gains an optional parameter.
  • Normal SDK behavior remains unchanged unless a pydantic validation error occurs.
  • No changes affect shared utilities, CI configuration, collection metadata, or other plugin areas.

The flightctl-client SDK's pydantic model for ApplicationVolume declares
image as required, but RHEM returns mount-only volumes without image.
Add a fallback that catches pydantic ValidationError and retries with
raw JSON endpoints, bypassing pydantic deserialization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Fleet retrieval now supports model objects and dictionaries. Typed SDK validation failures can use raw JSON pagination with continuation tokens. Device and fleet call sites provide fallback endpoints, with tests covering errors, pagination, and dictionary fleets.

Changes

Flight Control retrieval

Layer / File(s) Summary
Raw pagination and typed fallback
plugins/inventory/flightctl.py, tests/unit/plugins/inventory/test_flightctl.py
Raw responses are paginated and API failures are wrapped in FlightctlApiException. Typed retrieval falls back only for Pydantic validation errors.
Retrieval endpoint wiring
plugins/inventory/flightctl.py
Device and fleet retrieval paths provide raw-response fallback endpoints.
Fleet record materialization
plugins/inventory/flightctl.py, tests/unit/plugins/inventory/test_flightctl.py
Fleet processing accepts objects with to_dict() and dictionary records. Tests validate dictionary fleet metadata handling.

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

Sequence Diagram(s)

sequenceDiagram
  participant InventoryPlugin
  participant FlightctlSDK
  participant RawFlightctlEndpoint
  InventoryPlugin->>FlightctlSDK: request typed device or fleet data
  FlightctlSDK-->>InventoryPlugin: typed data or Pydantic validation error
  InventoryPlugin->>RawFlightctlEndpoint: request raw JSON fallback
  RawFlightctlEndpoint-->>InventoryPlugin: page items and continuation token
  InventoryPlugin->>RawFlightctlEndpoint: request subsequent page
  RawFlightctlEndpoint-->>InventoryPlugin: remaining items
Loading

Possibly related PRs

Suggested labels: plugins, tests

Suggested reviewers: siddarthr56, dakcrowder


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error The new warning interpolates the full pydantic ValidationError; pydantic includes input_value, which can contain customer fields, credentials, or tokens in inventory data. Log only a generic fallback warning. Remove {e} from the warning and sanitize any API error bodies before exposing them through exceptions.
Ai-Attribution ❌ Error The PR mentions Claude Code, while feature commits use prohibited Co-Authored-By: Claude Opus 4.6 trailers. Replace each AI Co-Authored-By trailer with an accepted Assisted-by, Generated-by, or Made-with trailer.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: fixing the inventory crash caused by mount-only application volumes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
No-Hardcoded-Secrets ✅ Passed The PR adds no real credentials, passwords, API keys, private keys, or credential-bearing URLs; token-abc is only a low-entropy pagination fixture.
No-Weak-Crypto ✅ Passed The PR adds raw JSON pagination and OIDC handling, but uses no MD5, SHA1, DES, RC4, 3DES, Blowfish, or ECB APIs; no secret or token is compared with ordinary equality.
No-Injection-Vectors ✅ Passed The changed Python files contain no shell=True, eval/exec, pickle.loads, or os.system calls; the only yaml.load uses yaml.SafeLoader.
Ansible-Idempotency ✅ Passed The PR changes only the Flight Control inventory plugin and its unit tests; it adds no Ansible module or command/shell task that could affect idempotency.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/EDM-4980-pydantic-fallback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EfratIfergan added a commit that referenced this pull request Aug 6, 2026
These changes are now in a dedicated PR #63 on branch
bugfix/EDM-4980-pydantic-fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@EfratIfergan
EfratIfergan marked this pull request as ready for review August 10, 2026 05:49

@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

🤖 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 `@plugins/inventory/flightctl.py`:
- Around line 623-629: Expand the existing try block in the Flight Control API
retrieval flow to include json.loads(response.data) and the subsequent records,
metadata, and continue_token processing, so malformed JSON or response-shape
errors are re-raised as FlightctlApiException. Add a test covering invalid JSON
response data and assert the plugin exception is raised.

In `@tests/unit/plugins/inventory/test_flightctl.py`:
- Around line 966-987: Add -> None return annotations to the new test methods,
including test_real_pydantic_validation_error,
test_generic_exception_returns_false, and
test_non_pydantic_validation_error_returns_false. Annotate parameters and return
values for the related fixture helper functions in the referenced sections,
following the repository’s existing type-hint conventions.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 75e1c04f-1465-4e21-a7cd-1488c265e30c

📥 Commits

Reviewing files that changed from the base of the PR and between cfcb2b5 and b32443a.

📒 Files selected for processing (2)
  • plugins/inventory/flightctl.py
  • tests/unit/plugins/inventory/test_flightctl.py

Comment on lines +623 to +629
except Exception as e:
raise FlightctlApiException(f"Error retrieving data from Flight Control API: {e}") from e
data = json.loads(response.data)
records = data.get('items', [])
all_records.extend(records)
metadata = data.get('metadata', {})
continue_token = metadata.get('continue', None)

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 | 🟡 Minor | ⚡ Quick win

Wrap raw response decoding failures in FlightctlApiException.

json.loads(response.data) runs outside the try block. A malformed raw response raises JSONDecodeError instead of the plugin's actionable FlightctlApiException.

Include response decoding and response-shape processing in the protected block. Add a test with invalid JSON data.

Proposed fix
         try:
             response = list_func(
                 var_continue=continue_token,
                 label_selector=label_list,
                 field_selector=field_list,
                 limit=limit,
                 _headers=headers,
                 _request_timeout=request_timeout,
             )
-        except Exception as e:
-            raise FlightctlApiException(f"Error retrieving data from Flight Control API: {e}") from e
-        data = json.loads(response.data)
-        records = data.get('items', [])
-        all_records.extend(records)
-        metadata = data.get('metadata', {})
-        continue_token = metadata.get('continue', None)
+            data = json.loads(response.data)
+            records = data.get('items', [])
+            all_records.extend(records)
+            metadata = data.get('metadata', {})
+            continue_token = metadata.get('continue')
+        except Exception as exc:
+            raise FlightctlApiException(
+                f"Error retrieving data from Flight Control API: {exc}"
+            ) from exc
📝 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
except Exception as e:
raise FlightctlApiException(f"Error retrieving data from Flight Control API: {e}") from e
data = json.loads(response.data)
records = data.get('items', [])
all_records.extend(records)
metadata = data.get('metadata', {})
continue_token = metadata.get('continue', None)
try:
response = list_func(
var_continue=continue_token,
label_selector=label_list,
field_selector=field_list,
limit=limit,
_headers=headers,
_request_timeout=request_timeout,
)
data = json.loads(response.data)
records = data.get('items', [])
all_records.extend(records)
metadata = data.get('metadata', {})
continue_token = metadata.get('continue')
except Exception as exc:
raise FlightctlApiException(
f"Error retrieving data from Flight Control API: {exc}"
) from exc
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 624-624: Avoid specifying long messages outside the exception class

(TRY003)

🤖 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 `@plugins/inventory/flightctl.py` around lines 623 - 629, Expand the existing
try block in the Flight Control API retrieval flow to include
json.loads(response.data) and the subsequent records, metadata, and
continue_token processing, so malformed JSON or response-shape errors are
re-raised as FlightctlApiException. Add a test covering invalid JSON response
data and assert the plugin exception is raised.

Source: Path instructions

Comment on lines +966 to +987
def test_real_pydantic_validation_error(self):
try:
from pydantic import ValidationError, BaseModel

class StrictModel(BaseModel):
value: int

try:
StrictModel(value="not-an-int") # type: ignore[arg-type]
except ValidationError as exc:
self.assertTrue(_is_pydantic_validation_error(exc))
except ImportError:
self.skipTest("pydantic not installed")

def test_generic_exception_returns_false(self):
self.assertFalse(_is_pydantic_validation_error(ValueError("nope")))

def test_non_pydantic_validation_error_returns_false(self):
class ValidationError(Exception):
__module__ = "myapp.errors"

self.assertFalse(_is_pydantic_validation_error(ValidationError("nope")))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add type hints to the new test methods and fixture helpers.

The new methods omit return annotations. The fixture helpers also omit parameter annotations. Add -> None to test methods and annotate helper inputs and return values.

As per path instructions, “Use type hints for function signatures.”

Also applies to: 993-1029, 1035-1106, 1113-1127

🤖 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 `@tests/unit/plugins/inventory/test_flightctl.py` around lines 966 - 987, Add
-> None return annotations to the new test methods, including
test_real_pydantic_validation_error, test_generic_exception_returns_false, and
test_non_pydantic_validation_error_returns_false. Annotate parameters and return
values for the related fixture helper functions in the referenced sections,
following the repository’s existing type-hint conventions.

Sources: Path instructions, Linters/SAST tools

@SiddarthR56

Copy link
Copy Markdown
Contributor

Same SDK issue likely affects module_utils/api_module.py

This fix is scoped to the inventory plugin, but the root cause (SDK's ApplicationVolume requiring image) also hits api_module.py's call_api() — the shared chokepoint behind flightctl_resource_info, device/fleet modules, approvals, etc. get()/list() only catch ApiException, not pydantic's ValidationError, so e.g. flightctl_resource_info: kind=Device on a mount-only-volume device would still crash with a raw traceback.

Not blocking for this PR, but worth a follow-up: extract _is_pydantic_validation_error() into a shared location and apply the same raw-JSON fallback to call_api().

@EfratIfergan
EfratIfergan merged commit f5f941b into main Aug 11, 2026
8 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