45190 patch policy wrong installer (#46087)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #
Adds a missing `is_active=1` check to get the actual active installer
for the software automation's installer.

# Checklist for submitter

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

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.


## Testing

- [x] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually
- I tested by pinning the version of the app to the older one, then
unpinning and back and checked that the policy is associated to the
correct installer with the query below.
- I did not manually test updating to a new FMA version. It's possible
to do that easily by creating a new branch with a newer version and
referring FLEET_DEV_MAINTAINED_APPS_BASE_URL to it.
 
 ```
 SELECT 
  si.id, 
  si.team_id, 
  si.title_id, 
  si.filename, 
  si.version, 
  si.storage_id, 
  si.uploaded_at, 
  si.updated_at, 
  si.url, 
  si.fleet_maintained_app_id, 
  si.install_during_setup, 
  si.is_active, 
  si.patch_query, 
  si.http_etag,
  p.patch_software_title_id,
  p.software_installer_id,
  p.query
FROM software_installers  si
LEFT JOIN policies p on si.id = p.software_installer_id
WHERE filename LIKE "%<app name>%" AND platform = "darwin" AND
global_or_team_id = <team id>;
 ```

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

## Summary by CodeRabbit

* **Bug Fixes**
* Patch policies using software install automations now correctly
prioritize active installers, ensuring deployment uses the latest
appropriate version rather than inactive or outdated alternatives.

* **Tests**
* Added test scenarios to validate policy installer version selection
and active status during automation evaluation.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/46087?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Jonathan Katz
2026-05-22 15:11:23 -04:00
committed by GitHub
parent f21f0c13e2
commit 5d9b102a1b
3 changed files with 54 additions and 1 deletions
@@ -0,0 +1 @@
- Fixed a bug where patch policies with software install automations used an inactive, older installer and not the latest.
+2 -1
View File
@@ -1373,7 +1373,8 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
VPPAppsTeamsID *uint `db:"vat_id"`
}
err := sqlx.GetContext(ctx, queryerContext, &ids,
`SELECT id si_id, NULL vat_id FROM software_installers WHERE global_or_team_id = ? AND title_id = ?
`SELECT id si_id, NULL vat_id FROM software_installers
WHERE global_or_team_id = ? AND title_id = ? AND is_active = 1
UNION
SELECT NULL si_id, vat.id vat_id FROM vpp_apps_teams vat
JOIN vpp_apps va ON va.adam_id = vat.adam_id AND va.platform = vat.platform
@@ -27924,6 +27924,24 @@ func (s *integrationEnterpriseTestSuite) TestPatchPolicies() {
require.Equal(t, name, policy.Name)
}
checkPolicyInstaller := func(policyID uint, version string) {
var row struct {
Version string `db:"version"`
IsActive bool `db:"is_active"`
}
mysqltest.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
return sqlx.GetContext(ctx, q, &row,
`SELECT si.version, si.is_active
FROM policies p
JOIN software_installers si ON si.id = p.software_installer_id
WHERE p.id = ?`,
policyID,
)
})
require.Equal(t, version, row.Version)
require.True(t, row.IsActive)
}
createHostPolicyResults := func(host *fleet.Host, policy *fleet.Policy) {
distributedResp := submitDistributedQueryResultsResponse{}
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
@@ -28247,6 +28265,7 @@ func (s *integrationEnterpriseTestSuite) TestPatchPolicies() {
checkPolicy(listPolResp.Policies[0], spec.Name, "1.0", title.ID)
// This is only set if the automation is enable
require.Equal(t, title.ID, listPolResp.Policies[0].InstallSoftware.SoftwareTitleID)
checkPolicyInstaller(listPolResp.Policies[0].ID, "1.0")
// Now disable the automation
spec = &fleet.PolicySpec{
@@ -28325,6 +28344,38 @@ func (s *integrationEnterpriseTestSuite) TestPatchPolicies() {
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), fleet.ListTeamPoliciesRequest{}, http.StatusOK, &listPolResp, "page", "0")
require.Len(t, listPolResp.Policies, 1)
checkPolicy(listPolResp.Policies[0], spec.Name, "1.0", title.ID)
// Test 4: FMA upgraded again with two versions already cached.
spec = &fleet.PolicySpec{
Name: "team patch policy",
Query: "SELECT 1",
Team: team.Name,
Type: fleet.PolicyTypePatch,
FleetMaintainedAppSlug: "zoom/windows",
SoftwareTitleID: new(title.ID),
}
resetFMAState(states["/zoom/windows.json"], "1.3", []byte("ghi"), "")
s.DoJSON("POST", "/api/latest/fleet/software/batch",
batchSetSoftwareInstallersRequest{Software: []*fleet.SoftwareInstallerPayload{{Slug: ptr.String("zoom/windows")}}, TeamName: team.Name},
http.StatusAccepted, &resp,
"team_name", team.Name, "team_id", fmt.Sprint(team.ID),
)
waitBatchSetSoftwareInstallersCompleted(t, &s.withServer, team.Name, resp.RequestUUID)
applyResp = fleet.ApplyPolicySpecsResponse{}
s.DoJSON("POST", "/api/latest/fleet/spec/policies",
fleet.ApplyPolicySpecsRequest{Specs: []*fleet.PolicySpec{spec}},
http.StatusOK, &applyResp,
)
title = getActiveTitleForTeam(team.ID, "zoom")
listPolResp = fleet.ListTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), fleet.ListTeamPoliciesRequest{}, http.StatusOK, &listPolResp, "page", "0")
require.Len(t, listPolResp.Policies, 1)
checkPolicy(listPolResp.Policies[0], spec.Name, "1.3", title.ID)
checkPolicyInstaller(listPolResp.Policies[0].ID, "1.3")
})
t.Run("override and empty queries behave the same", func(t *testing.T) {