Add patch policies to software filtered policies (#41839)

Bug found while developing a policy related feature.

<img width="1872" height="426" alt="Screenshot 2026-03-17 at 12 30
55 PM"
src="https://github.com/user-attachments/assets/a97c52fe-5a60-4acf-920f-07622d54f56f"
/>
This commit is contained in:
Lucas Manuel Rodriguez
2026-03-17 16:30:38 -03:00
committed by GitHub
parent 8e9bc79a5e
commit 5dc7f00fcd
4 changed files with 125 additions and 5 deletions
+1 -1
View File
@@ -2685,7 +2685,7 @@ func (ds *Datastore) createAutomationClause(ctx context.Context, automationFilte
switch automationFilter {
case "software":
return " AND (p.software_installer_id IS NOT NULL OR p.vpp_apps_teams_id IS NOT NULL)", nil, nil
return " AND (p.software_installer_id IS NOT NULL OR p.vpp_apps_teams_id IS NOT NULL OR p.type = 'patch')", nil, nil
case "scripts":
return " AND p.script_id IS NOT NULL", nil, nil
case "calendar":
+37 -3
View File
@@ -7662,6 +7662,29 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
// Add an FMA.
fma, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ID: 1})
require.NoError(t, err)
payload := &fleet.UploadSoftwareInstallerPayload{
InstallScript: "hello",
PreInstallQuery: "SELECT 1",
PostInstallScript: "world",
StorageID: "storage1",
Filename: "maintained1",
Title: "Maintained1",
Version: "1.0",
Source: "apps",
Platform: "darwin",
BundleIdentifier: "fleet.maintained1",
UserID: user1.ID,
TeamID: nil,
ValidatedLabels: &fleet.LabelIdentsWithScope{},
FleetMaintainedAppID: &fma.ID,
}
_, titleID2, err := ds.MatchOrCreateSoftwareInstaller(context.Background(), payload)
require.NoError(t, err)
test.CreateInsertGlobalVPPToken(t, ds)
// create team1 app
@@ -7721,6 +7744,14 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
teamPatchPolicy, err := ds.NewTeamPolicy(ctx, 0, nil, fleet.PolicyPayload{
Name: "query 8",
Query: "SELECT 1;",
Type: fleet.PolicyTypePatch,
PatchSoftwareTitleID: &titleID2,
})
require.NoError(t, err)
// TODO: test ticket integration policies?
config := fleet.TeamConfig{}
@@ -7735,7 +7766,7 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
}, "")
require.NoError(t, err)
require.Len(t, merged, 7)
require.Len(t, merged, 8)
assert.Equal(t, gpol.ID, merged[0].ID)
assert.Equal(t, teamInstallerPolicy.ID, merged[1].ID)
assert.Equal(t, teamAppStorePolicy.ID, merged[2].ID)
@@ -7743,6 +7774,7 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
assert.Equal(t, teamCalendarPolicy.ID, merged[4].ID)
assert.Equal(t, teamConditionalPolicy.ID, merged[5].ID)
assert.Equal(t, teamWebhookPolicy.ID, merged[6].ID)
assert.Equal(t, teamPatchPolicy.ID, merged[7].ID)
// Test filters
merged, err = ds.ListMergedTeamPolicies(ctx, 0, fleet.ListOptions{
@@ -7750,9 +7782,10 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
OrderDirection: fleet.OrderAscending,
}, "software")
require.NoError(t, err)
require.Len(t, merged, 2)
require.Len(t, merged, 3)
assert.Equal(t, teamInstallerPolicy.ID, merged[0].ID)
assert.Equal(t, teamAppStorePolicy.ID, merged[1].ID)
assert.Equal(t, teamPatchPolicy.ID, merged[2].ID)
merged, err = ds.ListMergedTeamPolicies(ctx, 0, fleet.ListOptions{
OrderKey: "name",
@@ -7792,7 +7825,8 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
OrderDirection: fleet.OrderAscending,
}, fleet.ListOptions{}, "software")
require.NoError(t, err)
require.Len(t, policies, 2)
require.Len(t, policies, 3)
assert.Equal(t, teamInstallerPolicy.ID, policies[0].ID)
assert.Equal(t, teamAppStorePolicy.ID, policies[1].ID)
assert.Equal(t, teamPatchPolicy.ID, policies[2].ID)
}
+1 -1
View File
@@ -59,7 +59,7 @@ type PolicyPayload struct {
// Type is the policy type. It is 'dynamic' by default and 'patch' for patch policies.
Type string
// PatchSoftwareTitleID is the title id of the Fleet maintained app chcked by a patch policy.
// PatchSoftwareTitleID is the title id of the Fleet maintained app checked by a patch policy.
//
// Only applies to team policies with the patch type.
PatchSoftwareTitleID *uint
@@ -1343,6 +1343,92 @@ func (s *integrationEnterpriseTestSuite) TestTeamPolicies() {
require.Len(t, ts.Policies, 0)
}
func (s *integrationEnterpriseTestSuite) TestListTeamPoliciesAutomationTypeSoftware() {
t := s.T()
ctx := context.Background()
// Create a team
team, err := s.ds.NewTeam(ctx, &fleet.Team{
Name: "team_automation_type_" + t.Name(),
})
require.NoError(t, err)
// Upload a software installer for the software install policy
payload := &fleet.UploadSoftwareInstallerPayload{
InstallScript: "some install script",
Filename: "ruby.deb",
TeamID: &team.ID,
}
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
rubyTitleID := getSoftwareTitleID(t, s.ds, "ruby", "deb_packages")
// Upload a second software installer for the patch policy (supported for FMAs)
payload2 := &fleet.UploadSoftwareInstallerPayload{
InstallScript: "some install script",
Filename: "dummy_installer.pkg",
TeamID: &team.ID,
}
s.uploadSoftwareInstaller(t, payload2, http.StatusOK, "")
dummyTitleID := getSoftwareTitleID(t, s.ds, "DummyApp", "apps")
// Create a Fleet Maintained App and associate it with the dummy installer
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
res, err := q.ExecContext(ctx, `INSERT INTO fleet_maintained_apps (name, slug, platform, unique_identifier)
VALUES ('DummyApp', 'dummy-autofilter/darwin', 'darwin', 'com.example.dummy-autofilter')`)
require.NoError(t, err)
id, err := res.LastInsertId()
require.NoError(t, err)
_, err = q.ExecContext(ctx, `UPDATE software_installers SET fleet_maintained_app_id = ? WHERE global_or_team_id = ? AND title_id = ?`, id, team.ID, dummyTitleID)
require.NoError(t, err)
return nil
})
// Create a regular policy (no automation)
regularPolicy := teamPolicyResponse{}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), teamPolicyRequest{
Name: "regular policy",
Query: "SELECT 1;",
}, http.StatusOK, &regularPolicy)
// Create a software install policy
installPolicy := teamPolicyResponse{}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), teamPolicyRequest{
Name: "install software policy",
Query: "SELECT 1 FROM some_table;",
SoftwareTitleID: &rubyTitleID,
}, http.StatusOK, &installPolicy)
require.NotNil(t, installPolicy.Policy.InstallSoftware)
// Create a patch policy
patchPolicy := teamPolicyResponse{}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), teamPolicyRequest{
Type: ptr.String("patch"),
PatchSoftwareTitleID: &dummyTitleID,
}, http.StatusOK, &patchPolicy)
require.NotNil(t, patchPolicy.Policy.PatchSoftware)
require.Equal(t, fleet.PolicyTypePatch, patchPolicy.Policy.Type)
// List all policies (no filter) - should return all 3
listResp := listTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), nil, http.StatusOK, &listResp)
require.Len(t, listResp.Policies, 3)
// List with automation_type=software - should return install policy and patch policy only
listResp = listTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), nil, http.StatusOK, &listResp, "automation_type", "software")
require.Len(t, listResp.Policies, 2)
policyIDs := []uint{listResp.Policies[0].ID, listResp.Policies[1].ID}
assert.Contains(t, policyIDs, installPolicy.Policy.ID)
assert.Contains(t, policyIDs, patchPolicy.Policy.ID)
// List with merge_inherited and automation_type=software
listResp = listTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), nil, http.StatusOK, &listResp, "merge_inherited", "true", "automation_type", "software")
require.Len(t, listResp.Policies, 2)
policyIDs = []uint{listResp.Policies[0].ID, listResp.Policies[1].ID}
assert.Contains(t, policyIDs, installPolicy.Policy.ID)
assert.Contains(t, policyIDs, patchPolicy.Policy.ID)
}
func (s *integrationEnterpriseTestSuite) TestNoTeamPolicies() {
t := s.T()
ctx := context.Background()