Skip to content

RHINENG-21568: Use our local systems table when generating playbooks#1038

Open
marleystipich2 wants to merge 4 commits intomasterfrom
RHINENG-21568-2
Open

RHINENG-21568: Use our local systems table when generating playbooks#1038
marleystipich2 wants to merge 4 commits intomasterfrom
RHINENG-21568-2

Conversation

@marleystipich2
Copy link
Copy Markdown
Collaborator

@marleystipich2 marleystipich2 commented Mar 25, 2026

Summary by Sourcery

Use the local systems table as the primary source for system details when generating playbooks, falling back to Inventory only for missing systems and caching those results locally.

New Features:

  • Add a query to retrieve system details for playbook generation from the local systems table.
  • Expose system detail storage for reuse when backfilling missing systems into the local database.

Enhancements:

  • Update system resolution logic to combine local systems data with Inventory data and improve handling of missing systems.
  • Extend seed data to include ansible_host for a test system to support the new system details flow.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Mar 25, 2026

Reviewer's Guide

This PR changes playbook system resolution to prefer a local systems table before falling back to Inventory, adds a query helper for fetching system details from the local DB, re-exports the system storage helper for reuse, and updates seed data to include ansible_host for a test system.

Sequence diagram for resolving systems using local DB with Inventory fallback

sequenceDiagram
    participant GeneratorController as GeneratorController
    participant RemediationsQueries as RemediationsQueries
    participant DB as DB_systems_table
    participant Inventory as InventoryConnector
    participant WriteController as RemediationsWriteController

    GeneratorController->>GeneratorController: resolveSystems(issues, strict)
    GeneratorController->>GeneratorController: extract systemIds from issues

    GeneratorController->>RemediationsQueries: getSystemDetailsForPlaybook(systemIds)
    RemediationsQueries->>DB: SELECT id, hostname, ansible_hostname, display_name WHERE id IN systemIds
    DB-->>RemediationsQueries: systems rows
    RemediationsQueries-->>GeneratorController: systemsByIdFromDB

    GeneratorController->>GeneratorController: compute missingIds (not in systemsByIdFromDB)
    alt missingIds not empty
        GeneratorController->>Inventory: getSystemDetailsBatch(missingIds, true)
        Inventory-->>GeneratorController: inventoryData
        GeneratorController->>WriteController: storeSystemDetails(inventoryData)
        WriteController-->>GeneratorController: (async completion, errors logged if any)
        GeneratorController->>GeneratorController: merge systems = systemsByIdFromDB + inventoryData
    else no missingIds
        GeneratorController->>GeneratorController: systems = systemsByIdFromDB
    end

    alt strict is false
        GeneratorController->>GeneratorController: filter issue.systems to ids present in systems
        GeneratorController->>GeneratorController: remove issues with no remaining systems
    end

    GeneratorController->>GeneratorController: map issue.systems to issue.hosts using systems
    GeneratorController->>GeneratorController: throw error if any system id missing when strict is true
    GeneratorController-->>GeneratorController: return issues with resolved hosts
Loading

Class diagram for updated system resolution and storage helpers

classDiagram
    class GeneratorController {
        +resolveSystems(issues, strict)
    }

    class RemediationsQueries {
        +getPlaybookRunsWithDispatcherCounts(playbookRunIds)
        +getSystemDetailsForPlaybook(systemIds)
    }

    class RemediationsWriteController {
        +removeSystem(req, res)
        +storeSystemDetails(systemDetails)
    }

    class SystemsModel {
        +id: uuid
        +hostname: string
        +ansible_hostname: string
        +display_name: string
    }

    class InventoryConnector {
        +getSystemDetailsBatch(systemIds, bypassCache)
    }

    GeneratorController --> RemediationsQueries : uses getSystemDetailsForPlaybook
    RemediationsQueries --> SystemsModel : queries
    GeneratorController --> InventoryConnector : uses getSystemDetailsBatch
    GeneratorController --> RemediationsWriteController : uses storeSystemDetails
    RemediationsWriteController --> SystemsModel : persists systemDetails
Loading

File-Level Changes

Change Details Files
Resolve systems for playbook generation by preferring local DB records and backfilling missing systems from Inventory
  • Replace direct Inventory batch fetch with a call to a new local DB query helper that retrieves system details by ID
  • Identify system IDs missing from the local DB results and fetch their details from Inventory with cache bypass to ensure fresh ansible_host values
  • Asynchronously persist newly fetched Inventory system details into the local systems table for future reuse, without blocking playbook generation
  • Adjust strict/non-strict handling and trace messages to treat missing systems as absent from both the local table and Inventory
src/generator/generator.controller.js
Add query helper to fetch system details for playbook generation from the systems table
  • Introduce getSystemDetailsForPlaybook that returns system records keyed by id from the systems table
  • Select only id, hostname, ansible_hostname, and display_name columns and map ansible_hostname into the ansible_host field expected by the generator
  • Handle empty or missing system ID lists by returning an empty object to simplify callers
src/remediations/remediations.queries.js
Expose system detail storage helper and test data updates for ansible_host
  • Export storeSystemDetails from the remediations write controller so it can be reused by the generator controller to backfill the local DB
  • Extend the systems seeder SPECIAL_SYSTEMS entry to include ansible_host for an existing test system, aligning seed data with the new query shape
src/remediations/controller.write.js
db/seeders/20180101000000-systems.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • When computing missingIds, consider using Object.prototype.hasOwnProperty.call(systems, id) instead of id in systems to avoid accidental matches on object prototype properties and keep the existence checks consistent with the rest of resolveSystems.
  • In getSystemDetailsForPlaybook, it may be safer to explicitly use an $in condition (e.g. { id: { [Op.in]: systemIds } }) rather than relying on implicit array handling in where: { id: systemIds }, to make the query behavior clearer and more robust across Sequelize versions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When computing `missingIds`, consider using `Object.prototype.hasOwnProperty.call(systems, id)` instead of `id in systems` to avoid accidental matches on object prototype properties and keep the existence checks consistent with the rest of `resolveSystems`.
- In `getSystemDetailsForPlaybook`, it may be safer to explicitly use an `$in` condition (e.g. `{ id: { [Op.in]: systemIds } }`) rather than relying on implicit array handling in `where: { id: systemIds }`, to make the query behavior clearer and more robust across Sequelize versions.

## Individual Comments

### Comment 1
<location path="src/generator/generator.controller.js" line_range="147-148" />
<code_context>
+        trace.event(`Fetching ${missingIds.length} missing systems from Inventory...`);
+        // bypass cache as ansible_host may change so we want to grab the latest one
+        const inventoryData = await inventory.getSystemDetailsBatch(missingIds, true);
+        // Store Inventory systems our in our local systems table so we don't have to fetch from Inventory next time
+        storeSystemDetails(inventoryData).catch(err => log.warn({ err }, 'Failed to store system details'));
+        systems = { ...systems, ...inventoryData };
+    }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Reconsider fire-and-forget `storeSystemDetails` error handling and visibility.

Because this call is fire-and-forget with only a warn-level log, persistent failures in `storeSystemDetails` could silently desync Inventory from the local DB. Consider either turning this into a proper background job with metrics/alerts, or at least enriching the log (e.g., system count, ID range) and increasing severity so repeated failures are visible operationally.

Suggested implementation:

```javascript
        // bypass cache as ansible_host may change so we want to grab the latest one
        const inventoryData = await inventory.getSystemDetailsBatch(missingIds, true);

        // Store Inventory systems in our local systems table so we don't have to fetch from Inventory next time.
        // Fire-and-forget, but with enriched logging so persistent failures are operationally visible.
        storeSystemDetails(inventoryData).catch(err => {
            const storedCount = inventoryData ? Object.keys(inventoryData).length : 0;

            log.error(
                {
                    err,
                    storedCount,
                    requestedSystemIds: missingIds
                },
                'Failed to store system details fetched from Inventory; local systems DB may be out of sync'
            );
        });

        systems = { ...systems, ...inventoryData };

```

If your service already has metrics/alerting, consider:
1. Emitting a counter for failures (e.g., `metrics.increment('generator.store_system_details.failure', storedCount)` or equivalent) inside the `catch` block.
2. Optionally, surfacing a separate alert when the failure rate crosses a threshold over time.
These will need to follow whatever metrics/logging conventions and libraries exist elsewhere in the codebase.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@marleystipich2
Copy link
Copy Markdown
Collaborator Author

marleystipich2 commented Mar 25, 2026

This PR focuses on using the local systems table when generating playbooks so these endpoints will attempt the use the systems table during playbook generation:

GET /remediations/:id/playbook
POST /remediations/:id/playbook 
GET /remediations/download 
POST /playbook 

What I think we should handle in separate PRs/tickets:

  • Use systems table for read endpoints like GET /remediations and GET /remediations/:id (these endpoints use a different resolveSystems function)
  • Satellite/cert_auth playbook filtering (these 2 cases were making an extra call to Inventory in controller.read.js but I think this is a separate issue than calling Inventory when we're actually generating the playbook)
  • Adjust getSystemDetailsBatch to return owner_id so that we can get rid of getSystemProfileBatch and just use that one call to get all the info that we need
  • Refactor the resolveSystems functions

@rexwhite rexwhite self-requested a review March 27, 2026 16:44
@marleystipich2
Copy link
Copy Markdown
Collaborator Author

I realized we needed to handle the Inventory 404s correctly in the resolveSystems function in generator controller because we use the strict flags in there. If strict=true we're supposed to throw an UNKNOWN_SYSTEM error and if strict=false we're supposed to gracefully handle it and filter out the unknown systems.

There are some changes in #1049 that helped were needed for this but I didn't want to combine the PRs into one.. So there's some overlap between that PR and this one. I'll rebase this one after we merge 1049.

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.

1 participant