Skip to content

EDM-4977: fix inventory plugin to use OIDC password grant instead of basic auth - #62

Merged
EfratIfergan merged 4 commits into
mainfrom
bugfix/EDM-4977-oidc-password-grant
Aug 9, 2026
Merged

EDM-4977: fix inventory plugin to use OIDC password grant instead of basic auth#62
EfratIfergan merged 4 commits into
mainfrom
bugfix/EDM-4977-oidc-password-grant

Conversation

@EfratIfergan

@EfratIfergan EfratIfergan commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • The flightctl inventory plugin was sending HTTP Basic Auth when username/password credentials were provided, but the RHEM server only accepts Bearer tokens obtained via OIDC password grant — making username/password authentication completely non-functional
  • Added _oidc_password_grant() method that discovers the token endpoint via OIDC .well-known/openid-configuration and exchanges credentials for a JWT Bearer token, matching the flightctl CLI behavior
  • Removed dead Basic Auth code from _build_auth_headers() and updated DOCUMENTATION descriptions for username/password options

Test plan

  • 15 new unit tests covering OIDC grant success, fallback to access_token, all error paths (discovery failure, missing endpoint, HTTP error, empty response), payload verification, config setup integration, and ca_path passthrough
  • Full test suite (182 tests) passes with zero regressions
  • Manual test with RHEM instance using username/password inventory config

🤖 Generated with Claude Code

Affected areas

  • plugins/inventory/flightctl.py

    • Replaces HTTP Basic Auth with OIDC password-grant authentication.
    • Discovers the OIDC token endpoint through .well-known/openid-configuration.
    • Uses the access_token response field as a Bearer token.
    • Adds validation errors for configuration, discovery, request, and token failures.
    • Preserves organization assignment and passes ca_path to HTTPS requests.
    • Supports fleet data from SDK models and dictionaries.
    • Updates username and password documentation.
    • Removes obsolete Basic Auth handling.
  • tests/unit/plugins/inventory/test_flightctl.py

    • Adds 15 unit tests for Bearer authentication, OIDC authentication, error paths, payloads, discovery URLs, configuration integration, and CA handling.
    • All 182 tests pass.

API and compatibility

  • The public argument surface does not change.
  • Return values and exported declarations do not change.
  • Username/password authentication now uses OIDC instead of Basic Auth.
  • Existing Bearer-token authentication remains supported.
  • Deployments that require Basic Auth must provide compatible OIDC configuration.

Unaffected areas

No changes are reported for modules, shared utilities, connection plugins, integration tests, demo files, CI configuration, collection metadata, or changelogs.

Validation

Manual testing with a RHEM instance remains pending.

…ugin

The inventory plugin was sending HTTP Basic Auth when username/password
were provided, but the RHEM server only accepts Bearer tokens via OIDC.
This replaces the broken Basic Auth path with an OIDC password grant flow
that mirrors the flightctl CLI behavior.

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

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Username/password authentication now uses OIDC discovery and a password-grant token request. Connection setup stores the bearer token and clears submitted credentials. Fleet processing accepts model objects and dictionaries. Inventory retrieval calls now pass configured request timeouts.

Changes

Flight Control inventory integration

Layer / File(s) Summary
OIDC token exchange
plugins/inventory/flightctl.py, tests/unit/plugins/inventory/test_flightctl.py
The plugin discovers the OIDC provider, submits credentials, applies CA settings, selects a token, and validates configuration and request failures.
Connection setup and bearer headers
plugins/inventory/flightctl.py, tests/unit/plugins/inventory/test_flightctl.py
Connection setup performs OIDC authentication when required, clears credentials after success, and uses bearer-only headers.
Inventory retrieval and fleet handling
plugins/inventory/flightctl.py
Fleet processing accepts dictionaries and model representations. Device and fleet retrieval calls pass the configured request timeout.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InventoryPlugin
  participant OIDCDiscovery
  participant TokenEndpoint
  InventoryPlugin->>OIDCDiscovery: Discover token endpoint
  OIDCDiscovery-->>InventoryPlugin: Return endpoint metadata
  InventoryPlugin->>TokenEndpoint: Submit password-grant credentials
  TokenEndpoint-->>InventoryPlugin: Return bearer token
  InventoryPlugin->>InventoryPlugin: Store token and clear credentials
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 (3 errors)

Check name Status Explanation Resolution
No-Hardcoded-Secrets ❌ Error The PR adds credential-named literals in tests, including config.password='secret' and password='redhat', plus access-token fixtures such as 'pre-existing-token' and 'my-jwt-token'. Replace literal password and token values in test fixtures with runtime-generated non-secret values or approved scanner-safe fixtures.
No-Sensitive-Data-In-Logs ❌ Error _oidc_password_grant embeds the entire token-endpoint HTTP error body in ValidationException (lines 362-366), which Ansible can display and which may contain sensitive data. Do not include the raw response body in the exception. Return only the status and a sanitized, allow-listed error field after redaction.
Ai-Attribution ❌ Error The PR states it used Claude Code, and its four feature commits use Co-Authored-By: Claude Opus 4.6, which the check explicitly forbids for AI attribution. Replace the AI Co-Authored-By trailers with an allowed Assisted-by, Generated-by, or Made-with trailer, then amend the affected commits.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: replacing Basic Auth with OIDC password-grant authentication in the inventory plugin.
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-Weak-Crypto ✅ Passed The complete PR delta adds OIDC token exchange and Bearer headers, with no MD5, SHA1, DES, RC4, 3DES, Blowfish, ECB, crypto APIs, or secret comparisons found.
No-Injection-Vectors ✅ Passed The reviewed Python code has no shell=True, eval/exec, pickle.loads, or os.system usage; its only yaml.load call explicitly uses yaml.SafeLoader.
Ansible-Idempotency ✅ Passed The PR changes only the inventory plugin and its tests; it adds no Ansible module, command/shell task, changed=True reporting, or check_mode path.
✨ 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-4977-oidc-password-grant

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.

@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 `@tests/unit/plugins/inventory/test_flightctl.py`:
- Around line 550-557: Update test_no_basic_auth_ever_produced to assert the
exact return value of _build_auth_headers(config) directly, expecting None for a
configuration without an access token, username, or password-based Basic Auth
output; remove the conditional headers check.
- Line 3: Remove the unused PropertyMock symbol from the unittest.mock import
statement, keeping only the mock utilities used by the tests so the F401 lint
violation is resolved.
🪄 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: 48e83519-0145-4248-8698-0d171e6d0bb5

📥 Commits

Reviewing files that changed from the base of the PR and between c41d6e9 and c42b21b.

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

Comment thread tests/unit/plugins/inventory/test_flightctl.py Outdated
Comment thread tests/unit/plugins/inventory/test_flightctl.py Outdated
Resolve import conflict in test_flightctl.py: combine OIDC test imports
(json, PropertyMock, _build_auth_headers, ValidationException) with env
declaration test imports (ClassVar, yaml, DOCUMENTATION) from EDM-4975.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread plugins/inventory/flightctl.py Outdated
…covery

- Replace urllib.request.urlopen with ansible.module_utils.urls.open_url
  to pass Ansible sanity tests (replace-urlopen check)
- Fetch OIDC provider config from /api/v1/auth/config instead of
  hardcoding the discovery path and client_id
- Fix pep8 line-length violation in DOCUMENTATION block
- Remove unused PropertyMock import and unnecessary lambda (pylint)
- Make test_no_basic_auth_ever_produced assertion unconditional

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

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

Caution

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

⚠️ Outside diff range comments (3)
tests/unit/plugins/inventory/test_flightctl.py (3)

557-563: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

test_no_basic_auth_ever_produced now duplicates test_no_headers_when_no_token exactly.

After the assertion fix, both tests build the same config and assert the same assertIsNone(headers). The two names promise two different guarantees, but the bodies are identical, so the suite reports two passes for one behavior. A reader who later changes _build_auth_headers gets no extra signal from the second test.

Give the second test a distinct scenario. Assert that a config carrying both a token and credentials still yields only a Bearer header, which is the anti-Basic-Auth guarantee the name claims.

♻️ Proposed distinct scenario
     def test_no_basic_auth_ever_produced(self):
         config = MagicMock()
-        config.access_token = None
+        config.access_token = "my-jwt-token"
         config.username = "admin"
-        config.password = "secret"
+        config.password = "placeholder-password"
         headers = _build_auth_headers(config)
-        self.assertIsNone(headers)
+        self.assertEqual(headers, {"Authorization": "Bearer my-jwt-token"})
+        self.assertNotIn("Basic", headers["Authorization"])

As per path instructions: "Each test verifies one behavior".

🤖 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 557 - 563,
Update test_no_basic_auth_ever_produced to use a configuration containing both a
non-null access token and username/password credentials, then assert that
_build_auth_headers returns only the expected Bearer header and no Basic-Auth
credentials. Keep test_no_headers_when_no_token unchanged so each test covers a
distinct behavior.

Source: Path instructions


923-953: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These eight tests restate test_all_connection_options_have_env_declarations.

The table-driven test at lines 910-921 already asserts, for every option in EXPECTED_ENV_VARS, that the option exists, that env is a list, and that it contains the expected variable name. Each of these eight methods asserts a strict subset of that, and each is weaker because it pins only index 0.

Adding a ninth option therefore requires two edits in this file instead of one, and the table stays the real source of truth either way. Delete the eight per-option methods and keep the table-driven test.

🤖 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 923 - 953,
Remove the eight per-option test methods from the test class:
test_token_env_declaration, test_host_env_declaration,
test_username_env_declaration, test_password_env_declaration,
test_organization_env_declaration, test_config_file_env_declaration,
test_ca_path_env_declaration, and test_verify_ssl_env_declaration. Retain
test_all_connection_options_have_env_declarations as the single table-driven
source of truth for environment declarations.

955-960: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

test_env_vars_match_module_utils_convention cannot fail.

The loop reads expected_env from EXPECTED_ENV_VARS, which is a literal defined ten lines above in this same file, and then asserts that the literal starts with FLIGHTCTL_. No plugin code participates. The test passes even if DOCUMENTATION declares no env blocks at all, or declares AWS_TOKEN.

The docstring claims the test enforces the convention "used in module_utils/core.py", but nothing imports or reads module_utils/core.py.

Assert against the values parsed from DOCUMENTATION instead, so the plugin is the subject under test.

💚 Proposed assertion against the parsed documentation
     def test_env_vars_match_module_utils_convention(self):
         """Env var names must follow the FLIGHTCTL_ prefix convention used in module_utils/core.py."""
-        for option_name, expected_env in self.EXPECTED_ENV_VARS.items():
+        for option_name in self.EXPECTED_ENV_VARS:
             with self.subTest(option=option_name):
-                self.assertTrue(expected_env.startswith('FLIGHTCTL_'),
-                                f"Env var '{expected_env}' should start with FLIGHTCTL_")
+                for entry in self.options[option_name].get('env', []):
+                    self.assertTrue(
+                        entry['name'].startswith('FLIGHTCTL_'),
+                        f"Env var '{entry['name']}' should start with FLIGHTCTL_")

As per path instructions: "Assertions must be specific — not just 'no exception was raised'".

🤖 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 955 - 960,
Update test_env_vars_match_module_utils_convention to inspect the plugin’s
parsed DOCUMENTATION env definitions rather than iterating over the locally
defined EXPECTED_ENV_VARS literals. Extract the declared environment variable
names from DOCUMENTATION and assert each actual value starts with FLIGHTCTL_,
preserving a specific assertion that fails for missing or incorrectly named
declarations.

Source: Path instructions

🤖 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 276-286: Update _fetch_auth_config to emit a clear warning before
calling open_url when verify_ssl is false, indicating that TLS certificate
verification is disabled and credentials may be exposed during the OIDC
exchange. Keep the existing validated path and request behavior unchanged.
- Around line 310-320: Replace the hardcoded 10-second timeouts in the OIDC
authentication flow with the resolved request_timeout value. Update the callers
and signatures of _oidc_password_grant and _fetch_auth_config to accept and
propagate that timeout, and use it for every open_url call involved in
authentication while preserving the configured default and existing SSL/CA
handling.
- Around line 623-629: Update _get_data_raw to validate the RESTResponse HTTP
status before processing its body, and handle non-success responses by parsing
structured JSON errors when possible or raising a FlightctlApiException for
non-JSON bodies. Guard json.loads(response.data) against JSONDecodeError, and
ensure error payloads without items are not treated as an empty inventory;
preserve normal pagination for successful responses.

In `@tests/unit/plugins/inventory/test_flightctl.py`:
- Around line 858-888: Move test_oidc_grant_passes_ca_path_to_open_url from
TestSetupConnectionOidcIntegration into TestOidcPasswordGrant, remove its local
_resp helper, and use the existing self._mock_response helper when configuring
mock_open.side_effect. Preserve the current CA-path and certificate-validation
assertions.
- Around line 966-978: In
tests/unit/plugins/inventory/test_flightctl.py:966-978, scope the try/except
ImportError around only the pydantic import, then use
assertRaises(ValidationError) for StrictModel construction and assert
_is_pydantic_validation_error on the captured exception. In
tests/unit/plugins/inventory/test_flightctl.py:1053-1065, apply the same
import-only scoping and return None only when the import fails, so unexpected
successful construction raises normally.
- Around line 1076-1088: Strengthen test_pydantic_error_triggers_fallback by
asserting the exact arguments passed to fallback_func, including label_list,
field_list, limit, headers, and request_timeout. Keep the existing result
assertions, and use the values supplied to _get_data so the test verifies
selector and request-option forwarding through the fallback path.
- Around line 632-663: Add unit tests alongside test_no_oidc_provider_raises for
_oidc_password_grant covering auth configurations whose selected provider spec
omits issuer and clientId. Mock discovery responses with each field absent,
assert ValidationException is raised, and pin the corresponding user-facing
error text for both branches.

---

Outside diff comments:
In `@tests/unit/plugins/inventory/test_flightctl.py`:
- Around line 557-563: Update test_no_basic_auth_ever_produced to use a
configuration containing both a non-null access token and username/password
credentials, then assert that _build_auth_headers returns only the expected
Bearer header and no Basic-Auth credentials. Keep test_no_headers_when_no_token
unchanged so each test covers a distinct behavior.
- Around line 923-953: Remove the eight per-option test methods from the test
class: test_token_env_declaration, test_host_env_declaration,
test_username_env_declaration, test_password_env_declaration,
test_organization_env_declaration, test_config_file_env_declaration,
test_ca_path_env_declaration, and test_verify_ssl_env_declaration. Retain
test_all_connection_options_have_env_declarations as the single table-driven
source of truth for environment declarations.
- Around line 955-960: Update test_env_vars_match_module_utils_convention to
inspect the plugin’s parsed DOCUMENTATION env definitions rather than iterating
over the locally defined EXPECTED_ENV_VARS literals. Extract the declared
environment variable names from DOCUMENTATION and assert each actual value
starts with FLIGHTCTL_, preserving a specific assertion that fails for missing
or incorrectly named declarations.
🪄 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: 0aba135e-cf8e-4af7-9889-4c2a5a3fe378

📥 Commits

Reviewing files that changed from the base of the PR and between c42b21b and 3dbdba0.

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

Comment on lines +276 to +286
def _fetch_auth_config(self, host: str, verify_ssl: bool,
ca_path: str | None = None) -> dict:
auth_config_url = host.rstrip('/') + "/api/v1/auth/config"
try:
resp = open_url(auth_config_url, validate_certs=verify_ssl,
ca_path=ca_path, timeout=10)
return json.loads(resp.read())
except Exception as exc:
raise ValidationException(
f"Failed to fetch auth config from {auth_config_url}: {exc}"
) from exc

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Warn the user when TLS verification is disabled during the OIDC exchange.

verify_ssl reaches open_url as validate_certs. In _setup_connection_configuration, verify_ssl falls back to False when the user omits the option (lines 389-394). The plugin now sends the username and password to the auth-config endpoint and the token endpoint. With validate_certs=False, those credentials travel over a channel that is not authenticated, so an active network attacker can present any certificate and capture the password grant. The previous code never sent the password itself from this plugin, so this changed path raises the impact.

Severity: major. The plugin still works, but the security guarantee for credential transport is silently degraded.

The path instructions require a warning when verification is disabled. Emit one before the first request.

🔒️ Proposed warning on the unverified path
     def _fetch_auth_config(self, host: str, verify_ssl: bool,
                            ca_path: str | None = None) -> dict:
         auth_config_url = host.rstrip('/') + "/api/v1/auth/config"
+        if not verify_ssl:
+            self._display.warning(
+                "verify_ssl is disabled; the OIDC password grant will send "
+                "credentials without validating the server certificate."
+            )
         try:

As per path instructions: "Validate TLS certificates by default; warn when verification is disabled".

Also applies to: 355-360

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 276-276: Boolean-typed positional argument in function definition

(FBT001)


[warning] 284-286: 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 276 - 286, Update
_fetch_auth_config to emit a clear warning before calling open_url when
verify_ssl is false, indicating that TLS certificate verification is disabled
and credentials may be exposed during the OIDC exchange. Keep the existing
validated path and request behavior unchanged.

Source: Path instructions

Comment on lines +310 to +320
def _oidc_password_grant(self, host: str, username: str, password: str,
verify_ssl: bool, ca_path: str | None = None) -> str:
import urllib.error
import urllib.parse

auth_config = self._fetch_auth_config(host, verify_ssl, ca_path)
provider = self._select_oidc_provider(auth_config)
spec = provider.get("spec", {})
issuer = spec.get("issuer")
client_id = spec.get("clientId")
scopes = spec.get("scopes")

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 | 🔵 Trivial | ⚡ Quick win

Reuse the configured request_timeout instead of the hardcoded 10 seconds.

Every open_url call in this flow uses timeout=10. The plugin already exposes a request_timeout option with a default of 120.0, and _setup_connection_configuration reads it. A user who raises request_timeout for a slow IdP still gets a 10-second failure on authentication, and the inventory run aborts before any API call happens. The consequence is an inconsistent and undiscoverable timeout contract across the same plugin run.

Pass the resolved timeout into _oidc_password_grant and _fetch_auth_config.

Also applies to: 331-344

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 311-311: Boolean-typed positional argument in function definition

(FBT001)

🤖 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 310 - 320, Replace the hardcoded
10-second timeouts in the OIDC authentication flow with the resolved
request_timeout value. Update the callers and signatures of _oidc_password_grant
and _fetch_auth_config to accept and propagate that timeout, and use it for
every open_url call involved in authentication while preserving the configured
default and existing SSL/CA handling.

Comment thread plugins/inventory/flightctl.py Outdated
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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the installed flightctl client REST response type and the without_preload_content contract.
fd -t f 'rest.py' | xargs -r rg -n -C5 'class RESTResponse|def data|self\.status'
fd -t f 'device_api.py|fleet_api.py' | xargs -r rg -n -A20 'def list_devices_without_preload_content|def list_fleets_without_preload_content'

Repository: flightctl/flightctl-ansible

Length of output: 165


🌐 Web query:

openapi-generator python without_preload_content return type RESTResponse data status

💡 Result:

In the Python generator for OpenAPI Generator, the without_preload_content method is designed to return a low-level RESTResponse object [1][2]. This approach allows developers to handle the raw response (such as streaming data) without the library automatically loading the entire response body into memory or attempting to deserialize it [3]. Key characteristics of this return type: 1. Return Type: The method returns an instance of RESTResponse [1][2]. In the context of the synchronous generator, this RESTResponse is a wrapper around the underlying HTTP client's response object (typically urllib3.HTTPResponse) [3][4]. For the asynchronous generator, it wraps aiohttp.ClientResponse [5]. 2. Structure: The RESTResponse object provides access to key metadata and data attributes: - status: The HTTP status code of the response [4][5]. - reason: The HTTP reason phrase [4][5]. - headers: A dictionary or dictionary-like object containing the response headers [4][5]. - data: Initially None until the read() method is called [4][5]. - read: A method (or async method in the asyncio generator) that reads and returns the response content [1][4][5]. By using without_preload_content, you bypass the standard deserialization logic, making it the appropriate choice for operations where you need to stream large files or handle raw binary data directly [3][6]. In contrast, standard request methods return deserialized data models or an ApiResponse object that has already consumed the stream [2][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant installed package references:\n'
rg -n 'flightctl|flightctl-client|flightctl\.client|from flightctl|import flightctl' pyproject.toml setup.cfg setup.py requirements*.txt poetry.lock uv.lock . 2>/dev/null | sed -n '1,160p' || true

printf '\nInventory file outline:\n'
ast-grep outline plugins/inventory/flightctl.py --view expanded 2>/dev/null | sed -n '1,220p' || true

printf '\nInspect _get_data_raw and callers:\n'
sed -n '560,740p' plugins/inventory/flightctl.py | cat -n | sed 's/^/plugins\/inventory\/flightctl.py:/'

printf '\nSearch for generated flightctl client package metadata:\n'
python3 - <<'PY'
import importlib.util
import json, os, pathlib, sys
mod_names = ['flightctl.client.rest', 'flightctl', 'flightctl.client']
for name in mod_names:
    spec = importlib.util.find_spec(name)
    print(name, bool(spec), getattr(spec, 'origin', None), getattr(spec, 'submodule_search_locations', None))
PY

PY3=$(command -v python3)
printf '\nPython module files matching flightctl client rest:\n'
$PY3 - <<'PY'
import sys
import os.path as ospath
from pathlib import Path

site=sys.prefix
for root, dirs, files in os.walk(site):
    for name in files:
        if name == 'rest.py' or name == 'api_client.py':
            path=os.path.join(root, name)
            text=pathlib.Path(path).read_text(errors='ignore')
            if 'RESTResponse' in text or 'without_preload_content' in text or 'preload_content=False' in text:
                print(path)
                for i, line in enumerate(text.splitlines(), 1):
                    if 'RESTResponse' in line or 'without_preload_content' in line or 'class RESTResponse' in line or 'def data' in line or '.status' in line:
                        print(f'{path}:{i}: {line.strip()}')
PY

Repository: flightctl/flightctl-ansible

Length of output: 32990


🌐 Web query:

flightctl-client 1.2.1 Python source RESTResponse without_preload_content data status

💡 Result:

In the Python client library for flightctl (such as flightctl-client v1.2.1, which is generated using the OpenAPI Generator [1][2]), the without_preload_content suffix is a standard pattern for API endpoint methods that bypasses automatic deserialization and validation of the response body [3]. When you call an API method with the _without_preload_content suffix, it returns a RESTResponse object rather than a high-level model object [3][4]. Key characteristics of this object include: 1. Data Access: The raw response body is available via the .data attribute, which typically returns a Python byte string [3]. You must decode this manually if you need the content as text or JSON (e.g., json.loads(response.data.decode("utf-8"))) [5]. 2. Status Information: The RESTResponse object provides access to the HTTP status code and response headers, allowing you to inspect the raw server response [3][4]. 3. No Validation: Unlike standard API calls that use Pydantic or other schema validation tools to map JSON to objects, the without_preload_content variant returns the response stream directly [3][6]. This is useful for handling large payloads [6], streaming data [7], or bypassing strict client-side validation when working with legacy or unexpected API responses [5]. If you require both the parsed model and the raw HTTP information (status, headers), you should instead use the _with_http_info variant of the method, which provides an ApiResponse container [3][4]. Note that without_preload_content methods are specifically designed for scenarios where you wish to manage the raw response handling yourself [3][5].

Citations:


🌐 Web query:

site:github.com/flightctl-client Python RESTResponse "self.status" "self.data" "without_preload_content"

💡 Result:

Found it in flightctl/flightctl-python-client:

  • File: flightctl/rest.py
  • RESTResponse class: lines 755–784
  • self.status and self.data are initialized at lines 761 and 765.
  • The HTTP request uses preload_content=False throughout RESTClientObject.request() and returns RESTResponse(r) at line 1131. (github.com)

The repository is flightctl/flightctl-python-client. (github.com)

Citations:


Validate the HTTP status and guard the JSON decode in _get_data_raw.

without_preload_content returns the raw FlightControl client RESTResponse, so handle status and body content as the caller. If the API returns 401/500, parse the body as JSON after checking the status, otherwise raise or handle the non-JSON response to avoid unhandled json.JSONDecodeError. If an error body lacks items, return or raise instead of treating it as an empty inventory.

🧰 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, Update _get_data_raw
to validate the RESTResponse HTTP status before processing its body, and handle
non-success responses by parsing structured JSON errors when possible or raising
a FlightctlApiException for non-JSON bodies. Guard json.loads(response.data)
against JSONDecodeError, and ensure error payloads without items are not treated
as an empty inventory; preserve normal pagination for successful responses.

Comment on lines +632 to +663
@patch(OPEN_URL)
def test_no_oidc_provider_raises(self, mock_open):
mock_open.return_value = self._mock_response({"providers": []})
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("No OIDC provider found", str(ctx.exception))

@patch(OPEN_URL)
def test_oidc_discovery_failure_raises(self, mock_open):
mock_open.side_effect = [
self._mock_response(self.SAMPLE_AUTH_CONFIG),
Exception("Connection refused"),
]
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("OIDC discovery failed", str(ctx.exception))

@patch(OPEN_URL)
def test_missing_token_endpoint_raises(self, mock_open):
mock_open.side_effect = [
self._mock_response(self.SAMPLE_AUTH_CONFIG),
self._mock_response({}),
]
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("token_endpoint missing", str(ctx.exception))

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 | 🔵 Trivial | ⚡ Quick win

Add coverage for the missing issuer and missing clientId branches.

_oidc_password_grant raises ValidationException when the selected provider spec has no issuer (flightctl.py lines 322-325) or no clientId (lines 326-329). No test exercises either branch. Both are reachable with a real auth config from a partially configured Flight Control server, and both produce user-facing error text that nothing currently pins.

The consequence is that a later refactor could drop either guard, or change the message a user relies on, and the suite stays green.

💚 Proposed additional tests
+    `@patch`(OPEN_URL)
+    def test_missing_issuer_raises(self, mock_open):
+        config = {"providers": [{"metadata": {"name": "p"},
+                                 "spec": {"providerType": "oidc", "clientId": "c"}}]}
+        mock_open.return_value = self._mock_response(config)
+        with self.assertRaises(ValidationException) as ctx:
+            self.module._oidc_password_grant("https://host", "admin", "pass", False)
+        self.assertIn("no issuer URL", str(ctx.exception))
+
+    `@patch`(OPEN_URL)
+    def test_missing_client_id_raises(self, mock_open):
+        config = {"providers": [{"metadata": {"name": "p"},
+                                 "spec": {"providerType": "oidc",
+                                          "issuer": "https://idp.example.com"}}]}
+        mock_open.return_value = self._mock_response(config)
+        with self.assertRaises(ValidationException) as ctx:
+            self.module._oidc_password_grant("https://host", "admin", "pass", False)
+        self.assertIn("no clientId", str(ctx.exception))

As per path instructions: "Test both success and error/exception paths".

📝 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
@patch(OPEN_URL)
def test_no_oidc_provider_raises(self, mock_open):
mock_open.return_value = self._mock_response({"providers": []})
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("No OIDC provider found", str(ctx.exception))
@patch(OPEN_URL)
def test_oidc_discovery_failure_raises(self, mock_open):
mock_open.side_effect = [
self._mock_response(self.SAMPLE_AUTH_CONFIG),
Exception("Connection refused"),
]
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("OIDC discovery failed", str(ctx.exception))
@patch(OPEN_URL)
def test_missing_token_endpoint_raises(self, mock_open):
mock_open.side_effect = [
self._mock_response(self.SAMPLE_AUTH_CONFIG),
self._mock_response({}),
]
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("token_endpoint missing", str(ctx.exception))
`@patch`(OPEN_URL)
def test_no_oidc_provider_raises(self, mock_open):
mock_open.return_value = self._mock_response({"providers": []})
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("No OIDC provider found", str(ctx.exception))
`@patch`(OPEN_URL)
def test_missing_issuer_raises(self, mock_open):
config = {"providers": [{"metadata": {"name": "p"},
"spec": {"providerType": "oidc", "clientId": "c"}}]}
mock_open.return_value = self._mock_response(config)
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant("https://host", "admin", "pass", False)
self.assertIn("no issuer URL", str(ctx.exception))
`@patch`(OPEN_URL)
def test_missing_client_id_raises(self, mock_open):
config = {"providers": [{"metadata": {"name": "p"},
"spec": {"providerType": "oidc",
"issuer": "https://idp.example.com"}}]}
mock_open.return_value = self._mock_response(config)
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant("https://host", "admin", "pass", False)
self.assertIn("no clientId", str(ctx.exception))
`@patch`(OPEN_URL)
def test_oidc_discovery_failure_raises(self, mock_open):
mock_open.side_effect = [
self._mock_response(self.SAMPLE_AUTH_CONFIG),
Exception("Connection refused"),
]
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("OIDC discovery failed", str(ctx.exception))
`@patch`(OPEN_URL)
def test_missing_token_endpoint_raises(self, mock_open):
mock_open.side_effect = [
self._mock_response(self.SAMPLE_AUTH_CONFIG),
self._mock_response({}),
]
with self.assertRaises(ValidationException) as ctx:
self.module._oidc_password_grant(
"https://host", "admin", "pass", False
)
self.assertIn("token_endpoint missing", str(ctx.exception))
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 635-635: Use pytest.raises instead of unittest-style assertRaises

Replace assertRaises with pytest.raises

(PT027)


[warning] 637-637: Boolean positional value in function call

(FBT003)


[warning] 647-647: Use pytest.raises instead of unittest-style assertRaises

Replace assertRaises with pytest.raises

(PT027)


[warning] 649-649: Boolean positional value in function call

(FBT003)


[warning] 659-659: Use pytest.raises instead of unittest-style assertRaises

Replace assertRaises with pytest.raises

(PT027)


[warning] 661-661: Boolean positional value in function call

(FBT003)

🤖 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 632 - 663, Add
unit tests alongside test_no_oidc_provider_raises for _oidc_password_grant
covering auth configurations whose selected provider spec omits issuer and
clientId. Mock discovery responses with each field absent, assert
ValidationException is raised, and pin the corresponding user-facing error text
for both branches.

Source: Path instructions

Comment on lines +858 to +888
@patch("plugins.inventory.flightctl.open_url")
def test_oidc_grant_passes_ca_path_to_open_url(self, mock_open):
def _resp(body):
r = MagicMock()
r.read.return_value = json.dumps(body).encode()
return r

auth_config = {
"providers": [{
"metadata": {"name": "p"},
"spec": {
"providerType": "oidc",
"issuer": "https://idp.example.com",
"clientId": "c",
},
}],
}
mock_open.side_effect = [
_resp(auth_config),
_resp({"token_endpoint": "https://idp.example.com/token"}),
_resp({"id_token": "tok"}),
]

module = InventoryModule()
module._oidc_password_grant(
"https://host", "admin", "pass", True, "/path/to/ca.crt"
)

for call in mock_open.call_args_list:
self.assertTrue(call[1].get("validate_certs"))
self.assertEqual(call[1].get("ca_path"), "/path/to/ca.crt")

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 | 🔵 Trivial | ⚡ Quick win

Move this test into TestOidcPasswordGrant and drop the duplicated response helper.

The class docstring at line 767 states that TestSetupConnectionOidcIntegration verifies _setup_connection_configuration. This test calls _oidc_password_grant directly and never touches _setup_connection_configuration, so it sits in the wrong class. It also redefines _resp, which is a byte-for-byte copy of TestOidcPasswordGrant._mock_response.

The consequence is a DRY violation plus a misleading test taxonomy: someone auditing OIDC transport coverage reads TestOidcPasswordGrant, sees no TLS assertions, and concludes the CA-path behavior is untested.

Move the test to TestOidcPasswordGrant and call self._mock_response.

🧰 Tools
🪛 ast-grep (0.45.0)

[info] 861-861: use jsonify instead of json.dumps for JSON output
Context: json.dumps(body)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 Ruff (0.16.1)

[warning] 860-860: Missing return type annotation for private function _resp

(ANN202)


[warning] 883-883: Boolean positional value in function call

(FBT003)

🤖 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 858 - 888, Move
test_oidc_grant_passes_ca_path_to_open_url from
TestSetupConnectionOidcIntegration into TestOidcPasswordGrant, remove its local
_resp helper, and use the existing self._mock_response helper when configuring
mock_open.side_effect. Preserve the current CA-path and certificate-validation
assertions.

Comment thread tests/unit/plugins/inventory/test_flightctl.py Outdated
Comment on lines +1076 to +1088
def test_pydantic_error_triggers_fallback(self):
pydantic_exc = self._make_pydantic_error()
if pydantic_exc is None:
self.skipTest("pydantic not installed")

list_func = MagicMock(side_effect=pydantic_exc)
raw_items = [{'metadata': {'name': 'dev-1'}}]
fallback_func = MagicMock(return_value=self._make_raw_response(raw_items))

result = _get_data(list_func, fallback_list_func=fallback_func)
self.assertEqual(len(result), 1)
self.assertEqual(result[0]['metadata']['name'], 'dev-1')
fallback_func.assert_called_once()

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 | 🔵 Trivial | ⚡ Quick win

Assert the arguments forwarded to the fallback, not just that it was called.

fallback_func.assert_called_once() passes regardless of what _get_data forwards. _get_data passes label_list, field_list, limit, headers, and request_timeout into _get_data_raw (flightctl.py lines 667-674), and _get_devices_by_labels_and_fields depends on those selectors reaching the raw endpoint.

If the forwarding of field_list broke, the fallback would fetch every device instead of the filtered set. _fetch_fleet_devices would then place unrelated devices into a fleet group, and the inventory would be silently wrong rather than failing. This test would still pass.

Pin the selectors in the assertion.

💚 Proposed argument assertion
-        result = _get_data(list_func, fallback_list_func=fallback_func)
+        result = _get_data(
+            list_func,
+            field_list="metadata.owner = Fleet/f1",
+            limit=50,
+            fallback_list_func=fallback_func,
+        )
         self.assertEqual(len(result), 1)
         self.assertEqual(result[0]['metadata']['name'], 'dev-1')
-        fallback_func.assert_called_once()
+        fallback_func.assert_called_once()
+        kwargs = fallback_func.call_args[1]
+        self.assertEqual(kwargs['field_selector'], "metadata.owner = Fleet/f1")
+        self.assertEqual(kwargs['limit'], 50)

As per path instructions: "Assertions must be specific — not just 'no exception was raised'".

📝 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
def test_pydantic_error_triggers_fallback(self):
pydantic_exc = self._make_pydantic_error()
if pydantic_exc is None:
self.skipTest("pydantic not installed")
list_func = MagicMock(side_effect=pydantic_exc)
raw_items = [{'metadata': {'name': 'dev-1'}}]
fallback_func = MagicMock(return_value=self._make_raw_response(raw_items))
result = _get_data(list_func, fallback_list_func=fallback_func)
self.assertEqual(len(result), 1)
self.assertEqual(result[0]['metadata']['name'], 'dev-1')
fallback_func.assert_called_once()
def test_pydantic_error_triggers_fallback(self):
pydantic_exc = self._make_pydantic_error()
if pydantic_exc is None:
self.skipTest("pydantic not installed")
list_func = MagicMock(side_effect=pydantic_exc)
raw_items = [{'metadata': {'name': 'dev-1'}}]
fallback_func = MagicMock(return_value=self._make_raw_response(raw_items))
result = _get_data(
list_func,
field_list="metadata.owner = Fleet/f1",
limit=50,
fallback_list_func=fallback_func,
)
self.assertEqual(len(result), 1)
self.assertEqual(result[0]['metadata']['name'], 'dev-1')
fallback_func.assert_called_once()
kwargs = fallback_func.call_args[1]
self.assertEqual(kwargs['field_selector'], "metadata.owner = Fleet/f1")
self.assertEqual(kwargs['limit'], 50)
🤖 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 1076 - 1088,
Strengthen test_pydantic_error_triggers_fallback by asserting the exact
arguments passed to fallback_func, including label_list, field_list, limit,
headers, and request_timeout. Keep the existing result assertions, and use the
values supplied to _get_data so the test verifies selector and request-option
forwarding through the fallback path.

Source: Path instructions

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>

@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

Caution

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

⚠️ Outside diff range comments (1)
plugins/inventory/flightctl.py (1)

372-372: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prefer access_token for the Bearer credential.

When the OIDC token endpoint returns both fields, id_token | access_token stores the ID token as Configuration.access_token, and API calls use that token in the Bearer header. If the ID token is not valid as an API access token, authentication can fail. Select access_token first and keep id_token only as a fallback if Flight Control explicitly supports it.

Proposed fix
-        token = data.get("id_token") or data.get("access_token")
+        token = data.get("access_token") or data.get("id_token")

Add a unit test that returns both fields and asserts Configuration.access_token receives the access_token.

🤖 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` at line 372, Update the token selection in
the OIDC response handling to prefer data.get("access_token") and use
data.get("id_token") only as the fallback, so Configuration.access_token
receives the API credential when both exist. Add a unit test covering a response
containing both fields and assert that Configuration.access_token stores the
access_token value.
🤖 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`:
- Line 450: Update the fleet conversion loop in _populate_inventory_fleets to
accept both fleet model objects and raw dictionaries, avoiding to_dict() when
the item is already a dictionary while preserving model serialization. Add a
test covering a raw fleet dictionary passed to _populate_inventory_fleets.

---

Outside diff comments:
In `@plugins/inventory/flightctl.py`:
- Line 372: Update the token selection in the OIDC response handling to prefer
data.get("access_token") and use data.get("id_token") only as the fallback, so
Configuration.access_token receives the API credential when both exist. Add a
unit test covering a response containing both fields and assert that
Configuration.access_token stores the access_token value.
🪄 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: cf4692fc-f327-40d8-992f-10ac76f40f1f

📥 Commits

Reviewing files that changed from the base of the PR and between 3dbdba0 and 1dd5dd8.

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

@@ -334,6 +448,7 @@ def _populate_inventory_fleets(self, fleets: List[Any], config) -> None:
return

for fleet in [fleet.to_dict() for fleet in fleets]:

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

Preserve support for raw fleet dictionaries.

Line 450 calls to_dict() for every fleet. A raw dictionary raises AttributeError and aborts inventory generation. This breaks the stated model-or-dictionary fleet contract.

Proposed fix
-        for fleet in [fleet.to_dict() for fleet in fleets]:
+        for raw_fleet in fleets:
+            fleet = raw_fleet.to_dict() if hasattr(raw_fleet, "to_dict") else raw_fleet
             fleet = _convert_enums_to_strings(fleet)

Add a test that passes a raw fleet dictionary to _populate_inventory_fleets.

📝 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
for fleet in [fleet.to_dict() for fleet in fleets]:
for raw_fleet in fleets:
fleet = raw_fleet.to_dict() if hasattr(raw_fleet, "to_dict") else raw_fleet
fleet = _convert_enums_to_strings(fleet)
🤖 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` at line 450, Update the fleet conversion loop
in _populate_inventory_fleets to accept both fleet model objects and raw
dictionaries, avoiding to_dict() when the item is already a dictionary while
preserving model serialization. Add a test covering a raw fleet dictionary
passed to _populate_inventory_fleets.

@SiddarthR56

Copy link
Copy Markdown
Contributor

One thing worth flagging: this same issue likely affects the other modules too. plugins/module_utils/api_module.py (set_auth()) and plugins/module_utils/imagebuilder_module.py (_set_auth_headers()) both still send HTTP Basic Auth for username/password — so flightctl_resource, flightctl_resource_info, flightctl_certificate_management, flightctl_enrollment_config_info, and the image builder modules have the same issue.

Might be worth extracting the OIDC discovery/password-grant logic from _oidc_password_grant() into a shared helper (e.g. plugins/module_utils/oidc_auth.py) so it can be reused by api_module.py and imagebuilder_module.py as well

@EfratIfergan
EfratIfergan merged commit cfcb2b5 into main Aug 9, 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