Skip to content

Added variables in Android configuration profiles - #47750

Merged
ksykulev merged 9 commits into
mainfrom
45353-android-var-profile
Jun 18, 2026
Merged

Added variables in Android configuration profiles#47750
ksykulev merged 9 commits into
mainfrom
45353-android-var-profile

Conversation

@ksykulev

@ksykulev ksykulev commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Related issue: Resolves #41968

Checklist for submitter

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

  • 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), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Database migrations

  • Checked schema for all modified table for columns that will auto-update timestamps during migration.
  • Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects.
  • Ensured the correct collation is explicitly set for character columns (COLLATE utf8mb4_unicode_ci).

Summary by CodeRabbit

  • New Features

    • Added support for using Fleet variables ($FLEET_VAR_HOST_*) in Android configuration profiles, enabling per-host dynamic value substitution during deployment.
  • Improvements

    • Strengthened Android profile validation to reject unsupported Fleet variables and prevent invalid placements (for example, using variables in JSON object keys or non-string fields).
    • Enhanced deployment behavior when variables can’t be resolved for a host, marking affected profiles as delivery failed and avoiding partial policy application.
    • Improved Android per-host rollout by applying installs in staggered batches for smoother throughput.

@ksykulev
ksykulev requested a review from a team as a code owner June 17, 2026 14:24

@claude claude 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.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f0582de0-02e0-4d12-94e8-7790b597147b

📥 Commits

Reviewing files that changed from the base of the PR and between bc332d6 and e3e6876.

📒 Files selected for processing (1)
  • server/fleet/android.go

Walkthrough

This PR adds $FLEET_VAR_HOST_* variable support to Android MDM configuration profiles. A new database migration extends mdm_configuration_profile_variables with an android_profile_uuid column, unique index, cascading foreign key, and an updated exactly-one-non-null check constraint. MDMAndroidConfigProfile.ValidateUserProvided gains a validation step that checks discovered variables against an allow-list and enforces they appear only in JSON string values, with a new exported helper FindUnsupportedAndroidFleetVar factored out for reuse. The batchSetProfileVariableAssociationsDB datastore function now processes Android profiles instead of skipping them. validateFleetVariables in the API service layer is extended to scan Android profile JSON for variables. sendHostProfiles in the Android reconciliation path performs per-host variable substitution before merging profile JSON, marking profiles as MDMDeliveryFailed when substitution cannot be resolved. The software worker gains batching with stagger delays for makeAndroidAppAvailablePerHost.

Possibly related issues

  • #41968: This PR directly implements the feature described in that issue — supporting all $FLEET_VAR_HOST_* variables in Android configuration profiles, including string-value enforcement, unresolvable variable error display, and profile delivery failure handling.

Possibly related PRs

  • fleetdm/fleet#45139: Both PRs modify the BatchSetMDMProfiles/validateFleetVariables validation flow; this PR extends validateFleetVariables to extract Android $FLEET_VAR_HOST_* variables using the same variable discovery pattern.
  • fleetdm/fleet#46276: Both PRs modify server/mdm/android/service/profiles.go's sendHostProfiles flow — this PR adds per-host $FLEET_VAR_HOST_* substitution during profile merge, while the linked PR adds setting/checksum-aware behavior for the MDMAndroidProfilePayload.
  • fleetdm/fleet#47642: Directly related — that PR introduced the Fleet variable allow-list and substitution machinery for Android app config in server/fleet/android.go that this PR builds upon to add full per-host substitution and failure handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added variables in Android configuration profiles' directly and clearly summarizes the main objective of the changeset: adding support for Fleet variables in Android configuration profiles.
Linked Issues check ✅ Passed The PR implements all core coding requirements from issue #41968: variables can be substituted in Android profiles, validation ensures variables appear only in string values, and error handling returns invalid JSON errors and displays failures in the Host details modal.
Out of Scope Changes check ✅ Passed All changes are scoped to Android profile variable support. The Android app config batch processing optimization in software_worker.go is a tangential performance improvement; while not explicitly listed in requirements, it supports the core feature's profile application.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 45353-android-var-profile

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.

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.42336% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.23%. Comparing base (75a0a03) to head (e3e6876).
⚠️ Report is 19 commits behind head on main.

Files with missing lines Patch % Lines
server/mdm/android/service/profiles.go 63.15% 16 Missing and 5 partials ⚠️
server/worker/software_worker.go 0.00% 12 Missing ⚠️
server/fleet/android.go 76.92% 6 Missing and 3 partials ⚠️
...0260617194413_AddAndroidProfileVariableTracking.go 82.60% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #47750    +/-   ##
========================================
  Coverage   67.23%   67.23%            
========================================
  Files        3634     3637     +3     
  Lines      229815   230072   +257     
  Branches    11967    11928    -39     
========================================
+ Hits       154517   154695   +178     
- Misses      61423    61482    +59     
- Partials    13875    13895    +20     
Flag Coverage Δ
backend 68.85% <66.42%> (+<0.01%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

Base automatically changed from 45353-android-var-configs to main June 17, 2026 16:10
@ksykulev
ksykulev requested a review from Copilot June 17, 2026 16:10

Copilot AI 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.

Warning

  • Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.

Pull request overview

Adds support for $FLEET_VAR_HOST_* variable usage in Android configuration profiles (and Android managed app configurations), including validation, per-host substitution during delivery, and database tracking of which variables are used by Android profiles.

Changes:

  • Validate/allow-list Fleet variables in Android configuration profiles and Android app configurations, including rejecting unsupported variables.
  • Substitute supported Fleet variables per-host when delivering Android profiles and when applying Android managed app configurations.
  • Track Android profile ↔ Fleet variable associations in mdm_configuration_profile_variables via a new android_profile_uuid column + FK/unique index, with migration + tests.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
server/worker/software_worker.go Adds per-host substitution for Android app managed configurations (including a per-host policy application path).
server/service/mdm.go Includes Android profiles in Fleet variable discovery/tracking during BatchSetMDMProfiles.
server/service/mdm_test.go Updates batch profile tests to reflect Android variable validation behavior.
server/service/integration_android_software_test.go Adds integration coverage for supported vs unsupported variables in Android app configs (single + batch).
server/mdm/profiles/profile_variables.go Refactors IDP variable resolution into a reusable resolver for non-XML contexts.
server/mdm/profiles/android_appconfig.go Implements JSON-safe Fleet variable substitution and host/IDP resolution for Android JSON configs.
server/mdm/profiles/android_appconfig_test.go Unit tests for Android JSON variable substitution + escaping behavior.
server/mdm/android/service/profiles.go Substitutes Fleet variables per-host before merging/sending Android profiles; surfaces delivery failures on unresolvable vars.
server/fleet/android.go Validates supported variables and enforces “variables must be inside JSON string values” for Android profiles.
server/fleet/android_test.go Adds tests for Android app config validation and Android profile variable validation.
server/datastore/mysql/schema.sql Updates schema for Android profile variable tracking column/index/FK + migration status.
server/datastore/mysql/migrations/tables/20260616222258_AddAndroidProfileVariableTracking.go Migration adding android_profile_uuid to variable association table with constraints.
server/datastore/mysql/migrations/tables/20260616222258_AddAndroidProfileVariableTracking_test.go Migration test verifying column, uniqueness, check constraint, and cascade delete.
server/datastore/mysql/mdm.go Enables variable association batching for Android profiles (uses new column).
server/datastore/mysql/android.go Expands Android host lite query to include platform + hardware serial for substitution.
ee/server/service/vpp.go Validates Android app configuration payloads during batch association.
Files excluded by content exclusion policy (2)
  • changes/41968-android-profile-fleet-variables
  • changes/45353-android-var-software-configs

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/worker/software_worker.go
Comment on lines +228 to +232
for hostUUID := range hosts {
h, ok := hostByUUID[hostUUID]
if !ok {
continue // host may have been deleted since the job was queued
}

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.

This will be fixed by #47543

Comment thread server/mdm/android/service/profiles.go

Copilot AI 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.

Warning

  • Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.

Files excluded by content exclusion policy (1)
  • changes/41968-android-profile-fleet-variables

Comment thread server/mdm/android/service/profiles.go

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

🧹 Nitpick comments (1)
server/mdm/android/service/profiles.go (1)

682-726: 💤 Low value

Minor optimization opportunity: AndroidHostLiteByHostUUID may be called twice for the same host.

When profiles contain variables (line 703) and the policy subsequently changes (line 456), AndroidHostLiteByHostUUID is invoked twice for the same host within a single sendHostProfiles call.

This is functionally correct, but if you want to avoid the duplicate lookup, you could refactor to fetch the host data once at the top of sendHostProfiles and reuse it. Given that this is a lightweight lookup and only occurs when both conditions are true, this is a low-priority refinement.

🤖 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 `@server/mdm/android/service/profiles.go` around lines 682 - 726, The
`substituteProfileVarsForHost` function calls `AndroidHostLiteByHostUUID` to
fetch the Android host data, but this lookup is also performed elsewhere in the
`sendHostProfiles` function, resulting in a duplicate call for the same host. To
optimize this, refactor `sendHostProfiles` to fetch the Android host data once
at the beginning of the function and pass the result to
`substituteProfileVarsForHost` as an additional parameter instead of having it
perform its own lookup. Update the function signature of
`substituteProfileVarsForHost` to accept the pre-fetched Android host object and
use it directly, eliminating the duplicate `AndroidHostLiteByHostUUID` call.
🤖 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
`@server/datastore/mysql/migrations/tables/20260616222258_AddAndroidProfileVariableTracking_test.go`:
- Around line 45-46: The INSERT statement in the test is using an invalid
apple_profile_uuid value 'a-fake' which causes a foreign key constraint failure
before the CHECK constraint ck_mdm_configuration_profile_variables_exactly_one
can be validated. To properly test the CHECK constraint, replace the 'a-fake'
value with either a valid apple_profile_uuid that exists in the database or NULL
(depending on what the constraint actually requires), so the FK constraint
passes and the actual CHECK constraint validation is what gets tested.

In `@server/fleet/android.go`:
- Around line 136-141: The current validation using stringVars map only checks
if a variable name exists somewhere in the parsed JSON, but fails to reject
variables that appear in JSON keys even when the same variable also appears in a
string value. To fix this, modify the walkJSONForStringVars function to only
track variables found in JSON string values and exclude any variables found in
keys, or add separate validation logic to explicitly check that variables do not
appear as keys in the JSON structure before accepting them. This ensures the
"string value only" rule is properly enforced. Apply the same fix to the second
occurrence of this validation pattern at lines 147-156.

---

Nitpick comments:
In `@server/mdm/android/service/profiles.go`:
- Around line 682-726: The `substituteProfileVarsForHost` function calls
`AndroidHostLiteByHostUUID` to fetch the Android host data, but this lookup is
also performed elsewhere in the `sendHostProfiles` function, resulting in a
duplicate call for the same host. To optimize this, refactor `sendHostProfiles`
to fetch the Android host data once at the beginning of the function and pass
the result to `substituteProfileVarsForHost` as an additional parameter instead
of having it perform its own lookup. Update the function signature of
`substituteProfileVarsForHost` to accept the pre-fetched Android host object and
use it directly, eliminating the duplicate `AndroidHostLiteByHostUUID` call.
🪄 Autofix (Beta)

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

Plan: Pro

Run ID: e19ce4dc-2ae7-476d-b023-fda830d396f4

📥 Commits

Reviewing files that changed from the base of the PR and between abd70c7 and 3f7bad7.

📒 Files selected for processing (11)
  • changes/41968-android-profile-fleet-variables
  • server/datastore/mysql/mdm.go
  • server/datastore/mysql/migrations/tables/20260616222258_AddAndroidProfileVariableTracking.go
  • server/datastore/mysql/migrations/tables/20260616222258_AddAndroidProfileVariableTracking_test.go
  • server/datastore/mysql/schema.sql
  • server/fleet/android.go
  • server/fleet/android_test.go
  • server/mdm/android/service/profiles.go
  • server/service/mdm.go
  • server/service/mdm_test.go
  • server/worker/software_worker.go

Comment thread server/fleet/android.go
getvictor
getvictor previously approved these changes Jun 18, 2026

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

Looks good. Let a couple non-blocking comments.

hostProfilesContents, varSubErr := substituteProfileVarsForHost(ctx, r.DS, hostUUID, profilesContents)
if varSubErr != nil {
if !errors.Is(varSubErr, profiles.ErrUnresolvableAndroidAppConfigVar) {
return nil, ctxerr.Wrapf(ctx, varSubErr, "substitute fleet vars for host %s", hostUUID)

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.

So if 1 profile has a bad var, then none get delivered? Is this desired/expected behavior?

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.

Oddly enough that's how it works right now. I would be open to changing that behavior because it's certainly strange to me as well. Thoughts?

Comment thread server/fleet/android.go
Comment on lines +125 to +134
found := variables.Find(string(rawJSON))
if len(found) == 0 {
return nil
}

for _, name := range found {
if !slices.Contains(FleetVarsSupportedInAndroidAppConfig, FleetVarName(name)) {
return fmt.Errorf("Couldn't edit profile. Unsupported Fleet variable $FLEET_VAR_%s.", name)
}
}

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.

Nit. Feels like this part (find variables and check them against a list) is generic and can be reused across the codebase.

Comment thread server/fleet/android.go
}

// walkJSONForVars recursively walks a decoded JSON value and collects fleet
// variable names found in string values and in map keys separately.

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.

Will there be a doc update that for Android variables behave differently than for Apple DDM, where they can be anywhere?

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.

Android profile keys must be real androidmanagement.Policy field names like "name", "wifiConfigsLockdownEnabled", "maximumTimeToLock", etc. So $FLEET_VAR_HOST_* won't ever match that, not before substitution, not after.

I can definitely update the docs so it's not confusing. I'll create a docs PR.

@ksykulev
ksykulev merged commit b32ceb7 into main Jun 18, 2026
45 checks passed
@ksykulev
ksykulev deleted the 45353-android-var-profile branch June 18, 2026 18:53
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.

Android: support all FLEET_VAR_HOST_ variables in configuration profiles

3 participants