Skip to content

Re-adding and optimizing macos software name renaming on ingestion - #33094

Closed
ksykulev wants to merge 7 commits into
mainfrom
29053-sw-names-ingest
Closed

Re-adding and optimizing macos software name renaming on ingestion#33094
ksykulev wants to merge 7 commits into
mainfrom
29053-sw-names-ingest

Conversation

@ksykulev

@ksykulev ksykulev commented Sep 17, 2025

Copy link
Copy Markdown
Contributor

Fixes #29053

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements)

Testing

  • Added/updated automated tests
  • Where appropriate, [automated tests simulate multiple hosts and test for host isolation]
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • Bug Fixes
    • Prevents duplicate macOS software entries when an app is renamed on the host, ensuring accurate inventory and consistent app names.
  • Performance
    • Optimized software ingestion with batched updates to reduce processing time and improve reliability during inventory syncs.
  • Documentation
    • Added changelog entry describing the macOS software ingestion correction and optimization.

@codecov

codecov Bot commented Sep 17, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.67568% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.92%. Comparing base (048fcd1) to head (5762637).
⚠️ Report is 92 commits behind head on main.

Files with missing lines Patch % Lines
server/datastore/mysql/software.go 75.67% 13 Missing and 5 partials ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #33094    +/-   ##
========================================
  Coverage   63.92%   63.92%            
========================================
  Files        2052     2050     -2     
  Lines      202440   202725   +285     
  Branches     6627     6555    -72     
========================================
+ Hits       129416   129601   +185     
- Misses      62832    62912    +80     
- Partials    10192    10212    +20     
Flag Coverage Δ
backend 65.08% <75.67%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.


// First, we remove incoming software that already exists in the software table.
if len(softwareChecksums) > 0 {
if len(softwareChecksums) > 0 || len(existingBundleIDsToUpdate) > 0 {

@ksykulev ksykulev Sep 17, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Is adding || len(existingBundleIDsToUpdate) > 0 ok?
existingSoftware is from getSoftwareIDsByChecksums which gets existing software ids for checksums. existingSoftware should only contain software that corresponds to the incoming checksums (softwareChecksums). However, the new renaming logic changed how macos software gets added to softwareChecksums and existingSoftware:

 for bid := range existingBundleIDsToUpdate {
 	if cs, ok := bundleIDsToChecksum[bid]; ok {
 		// we don't want this to be treated as a new software title, because then a new software
 		// entry will be created. Instead, we want to update the existing entries with the new
 		// names.
 		delete(incomingChecksumToSoftware, cs)
 	}
 }

This means that software gets removed from softwareChecksums but still exists in existingSoftware. Any macos software that is present in existingBundleIDsToUpdate will always trigger the !ok case. We don't have to worry about this software accidentally getting added to insertedSoftware, even if len(softwareChecksums) == 0.

@ksykulev
ksykulev marked this pull request as ready for review September 17, 2025 13:37
@ksykulev
ksykulev requested a review from a team as a code owner September 17, 2025 13:37
Comment thread server/datastore/mysql/software.go Outdated
strings.Join(updateIDs, ","),
)

if _, err := tx.ExecContext(ctx, updateStmt, updateArgs...); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIRC, one of the core issues last time was that this updating logic was executing inside an already very busy transaction. This lead to deadlocks, which lead to the DB issues.

I think we should do this update in a separate transaction.

@jahzielv

Copy link
Copy Markdown
Contributor

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 19, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a batch-oriented update path to rename existing macOS software entries on ingest using bundle identifiers and checksums. Modifies insert logic to return inserted and updated software sets, then applies targeted name updates post-insert. Updates tests to validate both insertion and rename flows. Adds a changelog entry.

Changes

Cohort / File(s) Summary
Datastore: software ingestion logic
server/datastore/mysql/software.go
Extends insertNewInstalledHostSoftwareDB to return (inserted, updated). Introduces updateTargetedBundleIDs for batched CASE UPDATEs and INSERTs into host_software. Adjusts applyChangesForNewSoftwareDB flow to handle both inserted and updated sets. Integrates common_mysql batching utilities. Updates error propagation for new return values.
Tests: software ingestion and rename handling
server/datastore/mysql/software_test.go
Adds testInsertNewInstalledHostSoftwareDB covering inserted/updated paths, OS-specific cases, and extensions. Refactors tests to consume new (inserted, updated) returns. Applies updateTargetedBundleIDs in tests to assert final renamed state and de-duplication. Updates test list to include new suite.
Changelog/Docs
changes/29053-sw-names-on-ingest
Documents re-adding and optimizing macOS software ingestion fix to prevent duplicates when software is renamed on host.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Host as Host
  participant Svc as Service Layer
  participant DS as Datastore (MySQL)
  participant DB as MySQL

  Host->>Svc: Ingest software inventory
  Svc->>DS: applyChangesForNewSoftwareDB(hostID, inventory)
  DS->>DB: insertNewInstalledHostSoftwareDB(..., checksums, ...)
  DB-->>DS: inserted[], updated[] (renamed-by-checksum)
  DS->>DB: updateTargetedBundleIDs(hostID, inserted[], updated[], existingBundleIDsToUpdate)
  note over DS,DB: Batched CASE UPDATE on software names<br/>Batched INSERT to host_software for last_opened_at
  DB-->>DS: OK
  DS-->>Svc: Completion
  Svc-->>Host: Ingest complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

#g-software

Suggested reviewers

  • iansltx
  • sharon-fdm

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues Check ❓ Inconclusive The changes reintroduce name-renaming logic and add batched updates and tests, addressing the core requirement to ensure newly-ingested macOS apps adopt the authoritative "better name" [#29053]. The linked issue also required that renames be performed without causing transaction contention (e.g., run outside long-lived transactions) and be restricted to exact matches (for example by checksum rather than broad bundle-id matching); the provided summaries confirm batching and targeted updates but do not explicitly state that the rename step runs outside the insertion transaction or that matching is strictly checksum-based. Because those two concurrency and targeting guarantees are not clearly evidenced in the summaries, I cannot conclusively verify full compliance with all listed requirements. Please confirm whether updateTargetedBundleIDs is executed outside the insertion transaction (or otherwise avoids long-lived transactions under high load) and whether updates are constrained by checksum or another unique identifier rather than broad bundle-id matching; if not, update the implementation or add tests demonstrating safe concurrent behavior and checksum-based targeting.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Re-adding and optimizing macos software name renaming on ingestion" accurately and concisely summarizes the primary change: reintroducing and improving macOS software-name renaming during ingestion (as reflected by the new batch rename logic, tests, and changelog). It is specific, short, and free of noise, though capitalizing "macOS" would match repository style. Overall the title aligns with the changeset and is clear to reviewers.
Out of Scope Changes Check ✅ Passed The modifications are focused on the macOS rename-on-ingest flow (server/datastore/mysql/software.go), associated tests, and a changes file; updated method signatures are internal to support the new flow. I do not see unrelated files or features modified outside the scope of issue #29053 in the provided summaries.
Description Check ✅ Passed The PR description references the issue and includes the repository checklist with relevant items checked (changes file added, input validation, and tests) while noting manual QA is pending, which matches the template sufficiently for review. It would be clearer if the author added the explicit "Related issue: Resolves #29053" header from the template and documented any DB migration considerations if applicable. Overall the description provides the necessary context and test coverage information for reviewers.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 29053-sw-names-ingest

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
server/datastore/mysql/software.go (1)

391-409: Move updateTargetedBundleIDs outside the transaction
The call on line 407 still lives inside ds.withRetryTxx, which contradicts the PR goal of running renaming logic outside the TX. Extract this (and any related rename work) to after the transaction completes.

🧹 Nitpick comments (6)
changes/29053-sw-names-on-ingest (1)

1-1: Capitalize macOS and tighten wording; omit issue/PR refs

Existing change notes don't include issue/PR references—mirror their capitalization and tone.

File: changes/29053-sw-names-on-ingest (line 1)

  • Re-add and optimize macOS software ingestion to prevent duplicate entries when an end-user renames an app on the host.
server/datastore/mysql/software.go (2)

423-497: Consider handling update errors more gracefully.

The updateTargetedBundleIDs function performs batch updates but if any batch fails, the entire operation fails. Consider implementing partial failure handling or retry logic for individual batches to improve resilience.

Consider wrapping the batch operations with error collection:

 const batchSize = 100
+var updateErrors []error
 return common_mysql.BatchProcessSimple(softwareIDs, batchSize, func(batch []uint) error {
     // ... existing batch processing code ...
     
     if _, err := tx.ExecContext(ctx, updateStmt, updateArgs...); err != nil {
-        return ctxerr.Wrap(ctx, err, "batch update software names")
+        updateErr := ctxerr.Wrap(ctx, err, fmt.Sprintf("batch update software names for batch starting at ID %d", batch[0]))
+        updateErrors = append(updateErrors, updateErr)
+        // Continue processing other batches
+        return nil
     }
     
     // ... rest of the code ...
 })
+
+if len(updateErrors) > 0 {
+    return fmt.Errorf("encountered %d errors during batch updates: %v", len(updateErrors), updateErrors[0])
+}

477-478: Hard-coded version string may cause maintenance issues.

The hard-coded string 'bundle_4.67' for name_source could make it difficult to track which version of the software performed the update. Consider making this configurable or version-aware.

Consider using a constant or configuration value:

+const bundleNameSource = "bundle_4.67" // Update this when the logic changes
+
 updateStmt := fmt.Sprintf(
-    `UPDATE software SET name = CASE id %s END, name_source = 'bundle_4.67' WHERE id IN (%s)`,
+    `UPDATE software SET name = CASE id %s END, name_source = '%s' WHERE id IN (%s)`,
     strings.Join(updateCases, " "),
+    bundleNameSource,
     strings.Join(updateIDs, ","),
 )
server/datastore/mysql/software_test.go (3)

266-273: Defer tx.Rollback() and avoid repeated tx boilerplate

In tests, a panic/require failure before Commit can leak an open transaction. Add a defer tx.Rollback() right after Beginx(), and consider a tiny helper to DRY the tx pattern.

Apply this pattern to each Beginx():

 tx, err := ds.writer(ctx).Beginx()
 require.NoError(t, err)
+defer tx.Rollback()

Also applies to: 296-301, 329-336, 392-399, 435-441, 483-489, 537-544


709-724: Add a negative control to prove rename is checksum‑scoped, not bundle‑wide

Issue #29053 requires targeting by checksum to avoid over‑broad renames. Add a second existing software row with the same bundle identifier but a different checksum/version, then verify only the intended row is renamed.

Example insertion and assertion snippet (adapt as needed):

@@
 // there's a new row for the new software, but existing software was renamed
 require.Len(t, software, 2)
 for _, s := range software {
   require.NotEmpty(t, s.TitleID)
   // software.name is updated now for GoLand
   if s.BundleIdentifier == "com.jetbrains.goland" {
     require.Equal(t, "GoLand 3.app", s.Name)
   }
 }
+// Insert another software with same bundle id but different checksum/version
+_, err = ds.writer(ctx).ExecContext(ctx, `
+  INSERT INTO software (name, version, source, bundle_identifier, checksum)
+  VALUES ('GoLand Legacy.app', '2023.1', 'apps', 'com.jetbrains.goland', UNHEX(MD5('gl-legacy')))
+`)
+require.NoError(t, err)
+// Re-run rename logic against a different incoming name; ensure legacy row is unaffected
+// (expect only the targeted checksum/name to change, not all rows sharing the bundle id)

811-813: Clarify variable names to match getExistingSoftware return order

The identifiers (incomingByChecksum, existingSoftware, existingTitlesForNewSoftware) don’t match the semantic order used elsewhere (currentSoftware, incomingChecksumToSoftware, incomingChecksumToTitle). Rename for clarity to prevent future mixups.

- incomingByChecksum, existingSoftware, existingTitlesForNewSoftware, _, err := ds.getExistingSoftware(
+ currentSoftware, incomingChecksumToSoftware, incomingChecksumToTitle, _, err := ds.getExistingSoftware(
     context.Background(), make(map[string]fleet.Software), incoming,
 )
@@
- _, _, err = ds.insertNewInstalledHostSoftwareDB(
-   context.Background(), tx, host1.ID, incomingByChecksum, existingSoftware, existingTitlesForNewSoftware, map[string]fleet.Software{},
+ _, _, err = ds.insertNewInstalledHostSoftwareDB(
+   context.Background(), tx, host1.ID, currentSoftware, incomingChecksumToSoftware, incomingChecksumToTitle, map[string]fleet.Software{},
 )

Also applies to: 841-843

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6506806 and 0294414.

📒 Files selected for processing (3)
  • changes/29053-sw-names-on-ingest (1 hunks)
  • server/datastore/mysql/software.go (12 hunks)
  • server/datastore/mysql/software_test.go (12 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • server/datastore/mysql/software_test.go
  • server/datastore/mysql/software.go
🧠 Learnings (2)
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: Fleet repo targets Go 1.24.5 (root go.mod), which supports testing.T.Context(). Do not flag usage of t.Context() or suggest replacing it with context.Background() in tests (e.g., server/datastore/mysql/labels_test.go Line 2031 and similar).

Applied to files:

  • server/datastore/mysql/software.go
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: In fleetdm/fleet repository tests (server/datastore/mysql/labels_test.go and similar), using testing.T.Context() is valid because the project targets a recent Go version where testing.T.Context() exists. Do not suggest replacing t.Context() with context.Background() in this codebase.

Applied to files:

  • server/datastore/mysql/software.go
🔇 Additional comments (10)
server/datastore/mysql/software.go (5)

811-811: Good addition for handling existing bundle IDs.

The condition || len(existingBundleIDsToUpdate) > 0 correctly ensures that software marked for renaming is processed even when softwareChecksums is empty.


799-1001: Updated function signature properly handles both inserted and updated software.

The changes to return both insertedSoftware and updatedSoftware correctly separate new entries from existing ones that need renaming, which aligns with the PR objectives.


820-821: Good handling of software marked for update.

The logic correctly identifies software that needs renaming and stores it in updatedSoftware for later processing.


456-496: Efficient batch processing implementation.

The use of common_mysql.BatchProcessSimple with a batch size of 100 and the combination of CASE statements for updates is an efficient approach that should handle the performance requirements mentioned in the PR objectives.


486-493: Verify INSERT IGNORE behavior for host_software.

Using INSERT IGNORE for host_software could silently skip updates to last_opened_at if a record already exists. Ensure this is the intended behavior.

Let me check if there's an ON DUPLICATE KEY UPDATE clause that might be more appropriate:

server/datastore/mysql/software_test.go (5)

41-41: Add test case to the suite: LGTM

Good coverage hook for the new ingestion/rename path.


245-572: New ingestion tests: solid scenarios; consider one more assertion

The cases exercise “new”, “update by bundle id”, mixed insert/update, Windows/Linux/Browser sources. Looks good.

As a follow‑up, consider asserting title_id presence in “Bundle id updates with empty softwareChecksums” (post insert) to mirror other tests’ guarantees.


645-656: Nice guard against duplicate host_software during rename

Validates no extra host_software rows are created after updateTargetedBundleIDs. Good.


741-743: Mechanical return-shape updates: LGTM

Call sites adjusted to the new (inserted, updated, err) signature.

Also applies to: 772-775


419-446: Drop argument-order verification
The test invocations already match the SoftwareFromOsqueryRow signature (name, version, source, vendor, installedPath, release, arch, bundleIdentifier, extensionId, browser, lastOpenedAt), so no parameter reordering is needed.

Comment thread server/datastore/mysql/software.go
// checking inserted software
for _, software := range insertedSoftware {
if software.BundleIdentifier != "" {
if updatedSoftware, needsUpdate := bundleIDsToSoftware[software.BundleIdentifier]; needsUpdate {

@sgress454 sgress454 Sep 23, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is the re-use of the updatedSoftware var name intentional here (and in the "check existing software" loop below)? Got me a bit turned around. Maybe updatedSoftwareID?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think my intention here was to highlight that this was a software that needs updating. Since bundleIDsToSoftware is a map[string]fleet.Software (aka bundle id => software).
However, I see the confusion here since we are also passing updatedSoftware into the func. Happy to change the name to something that makes sense.

@ksykulev

Copy link
Copy Markdown
Contributor Author

Changes were added to #33399

@ksykulev ksykulev closed this Sep 25, 2025
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.

🪲Existing macOS software names aren't fixed on software ingestion

3 participants