Skip to content

Clean up android app config data layer, fix no-team persist bug - #37740

Merged
iansltx merged 7 commits into
mainfrom
37729-android-app-configs
Dec 30, 2025
Merged

Clean up android app config data layer, fix no-team persist bug#37740
iansltx merged 7 commits into
mainfrom
37729-android-app-configs

Conversation

@iansltx

@iansltx iansltx commented Dec 30, 2025

Copy link
Copy Markdown
Contributor

Resolves #37729. Unreleased, so no changes file.

Checklist for submitter

If some of the following don't apply, delete the relevant line.

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

Testing

  • Added/updated automated tests

  • QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

  • Confirmed that the fix is not expected to adversely impact load test results

Summary by CodeRabbit

  • Refactor
    • Refactored Android app configuration storage and retrieval to use JSON-based format instead of structured objects.
    • Updated configuration lookups to use team-based identification consistently.
    • Added new method for retrieving Android app configurations by app team ID.

✏️ Tip: You can customize this high-level summary in your review settings.

…store methods

Still need to make another pass on this to clean up remaining struct fields, as we only actually need the config JSON for Gets
…for the by-VPP-app-team-ID data store method

One more refactoring step: dropping the struct declaration, since it's now only used in tests
Zed + Opus 4.5. Prompt: Remove indirection through fleet.AndroidAppConfiguration in all tests in server/datastore/mysql/android_test.go, then remove the struct definition.
…37729)

Zed + Opus 4.5. Prompt: Switch updateAndroidAppConfigurationTx's teamID parameter from a *uint to a uint, updating callers as needed.
…int" and use that to clean up the team_id cast
@iansltx

iansltx commented Dec 30, 2025

Copy link
Copy Markdown
Contributor Author

Assign of @RachelElysia is for QA as she has an Android env set up and I don't. Test coverage is better now than it was before, so this should just pass cleanly 🤞

@lucasmrod @getvictor Adding y'all as reviewers as there's currently no one but me around in SW until next Monday and y'all have worked closest to where this code is. Thx!

@iansltx iansltx changed the title Clean up android config data layer, fix no-team persist bug Clean up android app config data layer, fix no-team persist bug Dec 30, 2025
@codecov

codecov Bot commented Dec 30, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 65.86%. Comparing base (7535889) to head (9c914fc).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
server/datastore/mysql/vpp.go 75.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #37740      +/-   ##
==========================================
- Coverage   65.86%   65.86%   -0.01%     
==========================================
  Files        2362     2362              
  Lines      187339   187302      -37     
  Branches     7908     7908              
==========================================
- Hits       123395   123369      -26     
+ Misses      52663    52659       -4     
+ Partials    11281    11274       -7     
Flag Coverage Δ
backend 67.71% <96.42%> (-0.01%) ⬇️

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.

@lucasmrod lucasmrod left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@getvictor

Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Dec 30, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Dec 30, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Refactoring of Android app configuration datastore APIs to use json.RawMessage instead of structured fleet.AndroidAppConfiguration objects, with parameter renames from globalOrTeamID to teamID, removal of public insert/update methods, and introduction of an alternate lookup method by app-team ID.

Changes

Cohort / File(s) Summary
Datastore Interface
server/fleet/datastore.go, server/fleet/android.go
Updated Android configuration method signatures to use teamID instead of globalOrTeamID and return json.RawMessage instead of *fleet.AndroidAppConfiguration. Removed public AndroidAppConfiguration struct type and InsertAndroidAppConfiguration, UpdateAndroidAppConfiguration methods.
MySQL Implementation
server/datastore/mysql/android.go, server/datastore/mysql/android_test.go
Refactored to return json.RawMessage from retrieval methods; changed parameter binding from globalOrTeamID to teamID; removed InsertAndroidAppConfiguration and UpdateAndroidAppConfiguration public methods; added internal transaction-based updateAndroidAppConfigurationTx for configuration updates; BulkGetAndroidAppConfigurations now returns map[string]json.RawMessage.
VPP Integration
server/datastore/mysql/vpp.go, server/datastore/mysql/vpp_test.go
Updated callers of Android configuration methods to handle json.RawMessage returns and teamID parameters; adjusted pointer handling via ptr.ValOrZero() and ptr.UintOrNilIfZero() when invoking transaction methods.
Mock Datastore
server/mock/datastore_mock.go
Synchronized mock function signatures and return types with updated datastore interface; removed InsertAndroidAppConfigurationFunc and UpdateAndroidAppConfigurationFunc declarations and related invoked flags.
Worker
server/worker/software_worker.go
Updated makeAndroidAppAvailable to dereference json.RawMessage pointer directly instead of accessing a nested Configuration field.
Utility
server/ptr/ptr.go
Added UintOrNilIfZero(x uint) *uint function to convert zero-valued uints to nil pointers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • iansltx
  • dantecatalfamo

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: cleaning up the Android app config data layer and fixing the no-team persistence bug.
Description check ✅ Passed The description covers key requirements: input validation and SQL placeholders, automated tests added/updated, and load test impact confirmed for release candidates.
Linked Issues check ✅ Passed The PR addresses the core objective from #37729: fixing team_id = 0 handling for Android app configuration persistence via the PATCH endpoint by refactoring the data layer to use concrete teamID (uint) instead of nullable globalOrTeamID.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to the Android app configuration data layer: method signature updates, removal of unused public methods/types, and addition of a utility function for zero-value handling.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 37729-android-app-configs

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

🧹 Nitpick comments (1)
server/fleet/datastore.go (1)

2398-2410: Datastore Android app config interface matches JSON‑centric refactor; consider clarifying teamID semantics and naming

The updated methods:

  • GetAndroidAppConfiguration(ctx, applicationID string, teamID uint) (*json.RawMessage, error)
  • GetAndroidAppConfigurationByAppTeamID(ctx, vppAppTeamID uint) (*json.RawMessage, error)
  • HasAndroidAppConfigurationChanged(ctx, applicationID string, teamID uint, newConfig json.RawMessage) (bool, error)
  • BulkGetAndroidAppConfigurations(ctx, appIDs []string, teamID uint) (map[string]json.RawMessage, error)
  • DeleteAndroidAppConfiguration(ctx, adamID string, teamID uint) error

look consistent with the MySQL implementation and the new tests (JSON payloads and explicit teamID).

Two small polish suggestions:

  • Consider documenting explicitly in the comment that teamID == 0 represents “No team”, since that’s central to this bugfix and easy to mis‑handle in future call sites.
  • For DeleteAndroidAppConfiguration, renaming adamID to applicationID (or vice‑versa across the group) would make the parameter naming consistent with the other methods and reduce confusion between Apple/Android identifiers.

Functionally this interface change looks good; these are just minor clarity improvements.

📜 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 04d2a98 and 9c914fc.

📒 Files selected for processing (9)
  • server/datastore/mysql/android.go
  • server/datastore/mysql/android_test.go
  • server/datastore/mysql/vpp.go
  • server/datastore/mysql/vpp_test.go
  • server/fleet/android.go
  • server/fleet/datastore.go
  • server/mock/datastore_mock.go
  • server/ptr/ptr.go
  • server/worker/software_worker.go
🧰 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/ptr/ptr.go
  • server/datastore/mysql/vpp.go
  • server/fleet/android.go
  • server/datastore/mysql/vpp_test.go
  • server/worker/software_worker.go
  • server/datastore/mysql/android.go
  • server/fleet/datastore.go
  • server/datastore/mysql/android_test.go
  • server/mock/datastore_mock.go
🧠 Learnings (5)
📓 Common learnings
Learnt from: getvictor
Repo: fleetdm/fleet PR: 32515
File: server/datastore/mysql/migrations/tables/20250902112642_MigratePrimoFailingPoliciesAutomations.go:110-114
Timestamp: 2025-09-02T20:44:52.724Z
Learning: The app_config_json table in Fleet's MySQL database is guaranteed to have only 1 row, so UPDATE statements without WHERE clauses are safe and intentional for this table.
Learnt from: getvictor
Repo: fleetdm/fleet PR: 36978
File: server/mdm/android/service/profiles.go:441-449
Timestamp: 2025-12-11T23:02:59.284Z
Learning: Fleet’s server/mdm/android/service.Service has fields ds (type fleet.AndroidDatastore) and fleetDS (type fleet.Datastore). The fleet.Datastore interface embeds AndroidDatastore (server/fleet/datastore.go), so assigning r.DS (a fleet.Datastore) to Service.ds is valid in Go due to interface-to-interface assignment using the dynamic type.
📚 Learning: 2025-08-22T01:14:05.454Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 32173
File: server/datastore/mysql/policies.go:360-365
Timestamp: 2025-08-22T01:14:05.454Z
Learning: The sqlx library in Go (and the underlying database/sql package) can handle pointer types as query parameters correctly. Non-nil pointers are automatically dereferenced to get their values, and nil pointers are converted to SQL NULL values. This is standard, documented behavior - not a limitation or bug. Passing pointer parameters like `*uint`, `*string`, etc. to sqlx query methods is perfectly valid.

Applied to files:

  • server/ptr/ptr.go
📚 Learning: 2025-12-11T23:02:59.284Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 36978
File: server/mdm/android/service/profiles.go:441-449
Timestamp: 2025-12-11T23:02:59.284Z
Learning: Fleet’s server/mdm/android/service.Service has fields ds (type fleet.AndroidDatastore) and fleetDS (type fleet.Datastore). The fleet.Datastore interface embeds AndroidDatastore (server/fleet/datastore.go), so assigning r.DS (a fleet.Datastore) to Service.ds is valid in Go due to interface-to-interface assignment using the dynamic type.

Applied to files:

  • server/datastore/mysql/android.go
  • server/fleet/datastore.go
  • server/datastore/mysql/android_test.go
  • server/mock/datastore_mock.go
📚 Learning: 2025-08-08T08:32:31.529Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31695
File: server/datastore/mysql/apple_mdm_test.go:132-132
Timestamp: 2025-08-08T08:32:31.529Z
Learning: Datastore.NewMDMWindowsConfigProfile signature is: NewMDMWindowsConfigProfile(ctx context.Context, cp fleet.MDMWindowsConfigProfile, usesFleetVars []string) (*fleet.MDMWindowsConfigProfile, error). Passing nil for usesFleetVars in tests denotes “no Fleet variables referenced” and is used consistently across the repo.

Applied to files:

  • server/datastore/mysql/android_test.go
  • server/mock/datastore_mock.go
📚 Learning: 2025-10-03T18:16:11.482Z
Learnt from: MagnusHJensen
Repo: fleetdm/fleet PR: 33805
File: server/service/integration_mdm_test.go:1248-1251
Timestamp: 2025-10-03T18:16:11.482Z
Learning: In server/service/integration_mdm_test.go, the helper createAppleMobileHostThenEnrollMDM(platform string) is exclusively for iOS/iPadOS hosts (mobile). Do not flag macOS model/behavior issues based on changes within this helper; macOS provisioning uses different helpers such as createHostThenEnrollMDM.

Applied to files:

  • server/mock/datastore_mock.go
🧬 Code graph analysis (7)
server/datastore/mysql/vpp.go (1)
server/ptr/ptr.go (1)
  • ValOrZero (92-100)
server/datastore/mysql/vpp_test.go (2)
server/fleet/vpp.go (1)
  • VPPAppID (9-14)
server/fleet/datastore.go (1)
  • IsNotFound (2822-2828)
server/worker/software_worker.go (1)
server/ptr/ptr.go (1)
  • RawMessage (62-64)
server/datastore/mysql/android.go (2)
server/ptr/ptr.go (2)
  • RawMessage (62-64)
  • UintOrNilIfZero (27-32)
server/contexts/ctxerr/ctxerr.go (1)
  • Wrap (199-202)
server/fleet/datastore.go (1)
server/ptr/ptr.go (1)
  • RawMessage (62-64)
server/datastore/mysql/android_test.go (1)
server/ptr/ptr.go (2)
  • RawMessage (62-64)
  • T (86-88)
server/mock/datastore_mock.go (1)
server/ptr/ptr.go (1)
  • RawMessage (62-64)
🔇 Additional comments (18)
server/ptr/ptr.go (1)

24-32: LGTM!

The helper function is well-documented with a clear reference to the bug it addresses. The implementation correctly returns nil for zero values, enabling proper SQL NULL handling for "No team" scenarios.

server/worker/software_worker.go (1)

137-141: LGTM!

The nil check and dereference are correct for the new *json.RawMessage return type from GetAndroidAppConfigurationByAppTeamID. The logic correctly preserves the nil-config case where no configByAppID mapping is created.

server/datastore/mysql/vpp.go (3)

103-105: LGTM!

The dereference aligns with the API change where GetAndroidAppConfiguration now returns *json.RawMessage. The nil check on line 103 ensures safe dereferencing.


524-528: LGTM!

Using ptr.ValOrZero(teamID) correctly converts the nullable team ID to a uint value (0 for "No team"), which the refactored updateAndroidAppConfigurationTx now expects. This is part of the fix for issue #37729.


664-668: LGTM!

Consistent with the change in SetTeamVPPApps - using ptr.ValOrZero(teamID) to convert the nullable team ID to uint for the updated updateAndroidAppConfigurationTx signature.

server/datastore/mysql/vpp_test.go (2)

2598-2601: LGTM!

Correctly dereferences the *json.RawMessage returned by the updated GetAndroidAppConfiguration API.


2623-2634: Good test coverage for the new lookup method.

The test correctly validates GetAndroidAppConfigurationByAppTeamID by:

  1. Asserting consistency with GetAndroidAppConfiguration results when config exists
  2. Properly checking for NotFoundError when config is nil
server/fleet/android.go (1)

200-204: LGTM!

Minor formatting improvement separating the specific "unknown field" error case from the general JSON parsing error.

server/datastore/mysql/android_test.go (2)

2343-2400: Android app config insert/update/delete tests correctly exercise the teamID=0 (“No team”) path

The new tests around updateAndroidAppConfigurationTx and Get/BulkGet/DeleteAndroidAppConfiguration for teamID == 0 provide good coverage of the No‑team behavior and the JSON‐centric API:

  • Using json.RawMessage plus require.JSONEq and string comparisons against BulkGetAndroidAppConfigurations verifies both content and keying.
  • Calling the transactional helper with teamID 0 ensures the underlying bug (0 vs nil handling) is covered at the data layer.
  • The map length + value equality checks are sufficient to prove the expected appID key is present.

No issues from a correctness standpoint; these tests should effectively guard the No‑team regression going forward.


2429-2470: Team‑scoped and change‑detection tests for Android app configs look solid, with clear edge‑case coverage

The added/updated tests for:

  • testAndroidAppConfigurationCascadeDeleteTeam
  • testAndroidAppConfigurationGlobalVsTeam
  • testHasAndroidAppConfigurationChanged

collectively verify:

  • Team‑scoped configs are removed via FK cascade when a team is deleted, and lookups by (applicationID, teamID) correctly return “not found”.
  • Global (teamID == 0) vs team‑specific configs coexist and are independently retrievable, with JSON equality checked via require.JSONEq.
  • HasAndroidAppConfigurationChanged handles nuanced cases:
    • Differences in structure and value (empty string, {}, false, empty managedConfiguration, extra keys, completely different fields).
    • Equality for an unchanged config.
    • Presence/absence semantics when no row exists (empty new config vs non‑empty).

This is a nice, comprehensive matrix for the change‑detection helper and the new teamID semantics; I don’t see gaps that would block this PR.

Also applies to: 2541-2621

server/mock/datastore_mock.go (2)

1562-1572: Android app config mock function types correctly updated to new API

The updated function types now take teamID uint and use json.RawMessage/map[string]json.RawMessage, matching the refactored datastore API and allowing teamID == 0 to flow through as a valid value. This keeps the mock aligned with the production interface for the “No team” fix.


9621-9660: DataStore Android app config methods follow existing mock pattern

The new DataStore methods for Android app configuration correctly:

  • Lock/unlock and set the *FuncInvoked flags, and
  • Delegate to the corresponding *Func fields with the new (applicationID/adamID, teamID, newConfig) signatures.

This is consistent with the rest of the generated mocks and should work seamlessly with the updated tests.

server/datastore/mysql/android.go (6)

1678-1704: LGTM! Parameter refactoring is clean.

The signature change from globalOrTeamID to teamID uint and the consistent binding at line 1695 properly support the "No team" (teamID=0) case. The query correctly filters by global_or_team_id which is the NOT NULL column that stores 0 for "No team".


1706-1720: LGTM! Return type refactoring is correct.

The method now returns *json.RawMessage instead of a structured type, and the query has been optimized to select only the configuration column. The filtering by application_id and global_or_team_id (line 1708) ensures a unique result.


1722-1741: LGTM! JOIN-based retrieval is correct.

The alternate lookup method properly joins vpp_apps_teams and filters by the unique vat.id parameter. The return type change to *json.RawMessage is consistent with the refactoring pattern.


1743-1775: LGTM! Bulk retrieval correctly refactored.

The method signature and return type have been properly updated to use teamID uint and return map[string]json.RawMessage. The anonymous struct at lines 1761-1764 appropriately captures the query results, and the filtering by application_id IN (?) AND global_or_team_id = ? ensures correct scoping.


1805-1826: LGTM! Deletion properly scoped.

The method correctly uses teamID uint and filters by both application_id and global_or_team_id to ensure the deletion is properly scoped. The row count check at lines 1816-1823 appropriately detects if no record was found.


1829-1848: LGTM! Core bug fix for #37729 is correct.

The signature change from teamID *uint to teamID uint (line 1829) and the binding logic at line 1843 properly address the "No team" persist bug:

  • ptr.UintOrNilIfZero(teamID) is used for the nullable team_id column (converts 0 to NULL)
  • teamID is used directly for the NOT NULL global_or_team_id column (0 represents "No team")

This dual-column approach allows team_id=0 configurations to be persisted correctly. Input validation at line 1830 and parameterized queries provide proper safeguards.

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@RachelElysia

Copy link
Copy Markdown
Member

Just tested this branch, lgtm!

Screen.Recording.2025-12-30.at.10.31.11.AM.mov

@RachelElysia RachelElysia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

✅ works e2e

@iansltx
iansltx merged commit 405e10f into main Dec 30, 2025
45 checks passed
@iansltx
iansltx deleted the 37729-android-app-configs branch December 30, 2025 15:35
iansltx added a commit that referenced this pull request Dec 30, 2025
Resolves #37729. Unreleased, so no changes file.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)

## Testing

- [x] Added/updated automated tests

- [ ] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [x] Confirmed that the fix is not expected to adversely impact load
test results

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Refactored Android app configuration storage and retrieval to use
JSON-based format instead of structured objects.
* Updated configuration lookups to use team-based identification
consistently.
* Added new method for retrieving Android app configurations by app team
ID.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
iansltx added a commit that referenced this pull request Dec 30, 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.

Fleet API: Cannot edit configuration for android software for "No team"

4 participants