Delete unnecessary patch policies in batch set software installers (#43112)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #42991 

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

- [ ] 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.
- [ ] Timeouts are implemented and retries are limited to avoid infinite
loops
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## 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
This commit is contained in:
Jonathan Katz
2026-04-07 15:58:29 -04:00
committed by GitHub
parent e8de86be83
commit 856830b7ca
3 changed files with 101 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
- Fixed bug where adding a patch policy for a new installer in the UI caused gitops runs that didn't include that installer to fail.
@@ -2112,6 +2112,14 @@ WHERE
team_id = ?
`
const deleteAllPatchPolicies = `
DELETE FROM
policies
WHERE
team_id = ? AND
type = 'patch'
`
const deleteAllPendingUninstallScriptExecutions = `
DELETE FROM host_script_results WHERE execution_id IN (
SELECT execution_id FROM host_software_installs WHERE status = 'pending_uninstall'
@@ -2256,6 +2264,14 @@ WHERE
)
`
const deletePatchPoliciesWithInstallersNotInList = `
DELETE FROM
policies
WHERE
team_id = ? AND
patch_software_title_id NOT IN (?)
`
const countInstallDuringSetupNotInList = `
SELECT
COUNT(*)
@@ -2486,6 +2502,10 @@ WHERE
return ctxerr.Wrap(ctx, err, "unset all obsolete installers in policies")
}
if _, err := tx.ExecContext(ctx, deleteAllPatchPolicies, globalOrTeamID); err != nil {
return ctxerr.Wrap(ctx, err, "delete all obsolete patch policies")
}
if _, err := tx.ExecContext(ctx, deleteAllPendingUninstallScriptExecutions, globalOrTeamID); err != nil {
return ctxerr.Wrap(ctx, err, "delete all pending uninstall script executions")
}
@@ -2586,6 +2606,14 @@ WHERE
return ctxerr.Wrap(ctx, err, "unset obsolete software installers from policies")
}
stmt, args, err = sqlx.In(deletePatchPoliciesWithInstallersNotInList, globalOrTeamID, titleIDs)
if err != nil {
return ctxerr.Wrap(ctx, err, "build statement to delete obsolete patch policies")
}
if _, err := tx.ExecContext(ctx, stmt, args...); err != nil {
return ctxerr.Wrap(ctx, err, "delete obsolete patch policies")
}
// check if any in the list are install_during_setup, fail if there is one
if !replacingInstallDuringSetup {
stmt, args, err = sqlx.In(countInstallDuringSetupNotInList, globalOrTeamID, titleIDs)
@@ -28184,3 +28184,75 @@ func (s *integrationEnterpriseTestSuite) TestPinMajorVersion() {
)
})
}
func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersDeletesObsoletePatchPolicy() {
t := s.T()
team, err := s.ds.NewTeam(context.Background(), &fleet.Team{Name: "team_" + t.Name()})
require.NoError(t, err)
// Set up two FMAs: zoom/windows and 1password/darwin.
states := make(map[string]*fmaTestState, 2)
states["/zoom/windows.json"] = &fmaTestState{
version: "1.0",
installerBytes: []byte("xyz"),
installerPath: "/zoom.msi",
}
states["/1password/darwin.json"] = &fmaTestState{
version: "1.0",
installerBytes: []byte("abc"),
installerPath: "/1password.pkg",
}
startFMAServers(t, s.ds, states)
// Setup: batch set only zoom/windows.
var resp batchSetSoftwareInstallersResponse
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)
// Batch set both zoom/windows and 1password/darwin.
s.DoJSON("POST", "/api/latest/fleet/software/batch",
batchSetSoftwareInstallersRequest{Software: []*fleet.SoftwareInstallerPayload{
{Slug: ptr.String("zoom/windows")},
{Slug: ptr.String("1password/darwin")},
}, TeamName: team.Name},
http.StatusAccepted, &resp,
"team_name", team.Name, "team_id", fmt.Sprint(team.ID),
)
waitBatchSetSoftwareInstallersCompleted(t, &s.withServer, team.Name, resp.RequestUUID)
// Create a patch policy for 1password/darwin.
applyResp := fleet.ApplyPolicySpecsResponse{}
s.DoJSON("POST", "/api/latest/fleet/spec/policies",
fleet.ApplyPolicySpecsRequest{Specs: []*fleet.PolicySpec{{
Name: "1password patch policy",
Team: team.Name,
Type: fleet.PolicyTypePatch,
FleetMaintainedAppSlug: "1password/darwin",
}}},
http.StatusOK, &applyResp,
)
// Verify the patch policy exists.
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)
require.Equal(t, fleet.PolicyTypePatch, listPolResp.Policies[0].Type)
// Batch set only zoom/windows — should not fail and should delete the obsolete patch policy.
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)
// Verify the patch policy was deleted.
listPolResp = fleet.ListTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), fleet.ListTeamPoliciesRequest{}, http.StatusOK, &listPolResp, "page", "0")
require.Empty(t, listPolResp.Policies)
}