31581 Fix packages_only flag to only show items with software_package (#32284)

Closes #31581

Note:
- When no team id is provided it lists all installers, but they don't
have software_package fielded. I don't know if this is the intended
behavior or not.

# 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

- [x] QA'd all new/changed functionality manually
This commit is contained in:
Jonathan Katz
2025-08-26 21:53:46 -04:00
committed by GitHub
parent 13d0195e2b
commit d3742e5227
4 changed files with 97 additions and 3 deletions
+11 -3
View File
@@ -113,6 +113,10 @@ func (ds *Datastore) ListSoftwareTitles(
return nil, 0, nil, fleet.NewInvalidArgumentError("query", "min_cvss_score, max_cvss_score, and exploit can only be provided with vulnerable=true")
}
if opt.TeamID == nil && opt.PackagesOnly {
return nil, 0, nil, fleet.NewInvalidArgumentError("query", "packages_only can only be provided with team_id")
}
dbReader := ds.reader(ctx)
getTitlesStmt, args, err := selectSoftwareTitlesSQL(opt)
if err != nil {
@@ -354,7 +358,8 @@ SELECT
{{end}}
FROM software_titles st
{{if hasTeamID .}}
LEFT JOIN software_installers si ON si.title_id = st.id AND si.global_or_team_id = {{teamID .}}
{{$installerJoin := printf "%s JOIN software_installers si ON si.title_id = st.id AND si.global_or_team_id = %d" (yesNo .PackagesOnly "INNER" "LEFT") (teamID .)}}
{{$installerJoin}}
LEFT JOIN vpp_apps vap ON vap.title_id = st.id AND {{yesNo .PackagesOnly "FALSE" "TRUE"}}
LEFT JOIN vpp_apps_teams vat ON vat.adam_id = vap.adam_id AND vat.platform = vap.platform AND
{{if .PackagesOnly}} FALSE {{else}} vat.global_or_team_id = {{teamID .}}{{end}}
@@ -395,8 +400,11 @@ WHERE
{{end}}
{{$additionalWhere}}
{{end}}
-- If teamID is set, defaults to "a software installer or VPP app exists", and see next condition.
{{with $defFilter := yesNo (hasTeamID .) "(si.id IS NOT NULL OR vat.adam_id IS NOT NULL)" "FALSE"}}
{{with $defFilter := "FALSE"}}
-- If teamID is set and PackagesOnly is false, defaults to "a software installer or VPP app exists", and see next condition.
{{if and (hasTeamID $) (not $.PackagesOnly) }}
{{$defFilter = "(si.id IS NOT NULL OR vat.adam_id IS NOT NULL)"}}
{{end}}
-- add software installed for hosts if we're not filtering for "available for install" only
{{if not $.AvailableForInstall}}
{{$defFilter = $defFilter | printf " ( %s OR sthc.hosts_count > 0 ) "}}
@@ -41,6 +41,7 @@ func TestSoftwareTitles(t *testing.T) {
{"UpdateSoftwareTitleName", testUpdateSoftwareTitleName},
{"ListSoftwareTitlesDoesnotIncludeDuplicates", testListSoftwareTitlesDoesnotIncludeDuplicates},
{"ListSoftwareTitlesAllTeamsWithAutomaticInstallersInNoTeam", testListSoftwareTitlesAllTeamsWithAutomaticInstallersInNoTeam},
{"ListSoftwareTitlesPackagesOnly", testSoftwareTitlesPackagesOnly},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@@ -2073,3 +2074,87 @@ func testListSoftwareTitlesAllTeamsWithAutomaticInstallersInNoTeam(t *testing.T,
require.Len(t, allTeamsTitles, 1)
require.Nil(t, allTeamsTitles[0].SoftwarePackage)
}
func testSoftwareTitlesPackagesOnly(t *testing.T, ds *Datastore) {
ctx := context.Background()
team1, err := ds.NewTeam(ctx, &fleet.Team{Name: "team1"})
require.NoError(t, err)
host := test.NewHost(t, ds, "host1", "", "host1key", "host1uuid", time.Now())
require.NoError(t, ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team1.ID, []uint{host.ID})))
user := test.NewUser(t, ds, "Alice", "alice@example.com", true)
software := []fleet.Software{
{Name: "foo", Version: "1.0.0", Source: "deb_packages"},
{Name: "bar", Version: "2.0.0", Source: "apps"},
{Name: "baz", Version: "3.0.0", Source: "rpm_packages"},
}
_, err = ds.UpdateHostSoftware(ctx, host.ID, software)
require.NoError(t, err)
_, _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "foo",
Source: "deb_packages",
InstallScript: "echo foo",
Filename: "foo.pkg",
UserID: user.ID,
TeamID: &team1.ID,
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
_, _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "bar",
Source: "apps",
InstallScript: "echo bar",
Filename: "bar.pkg",
UserID: user.ID,
TeamID: &team1.ID,
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
// Sync and reconcile
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
t.Run("packages_only=false no team_id", func(t *testing.T) {
titles, _, _, err := ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{
PackagesOnly: false,
}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 3)
})
t.Run("packages_only=true no team_id", func(t *testing.T) {
_, _, _, err := ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{
PackagesOnly: true,
}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.Error(t, err)
require.Contains(t, err.Error(), "packages_only can only be provided with team_id")
})
t.Run("packages_only=false with team_id", func(t *testing.T) {
titles, _, _, err := ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{
PackagesOnly: false,
TeamID: &team1.ID,
}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 3)
})
t.Run("packages_only=true with team_id", func(t *testing.T) {
titles, _, _, err := ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{
PackagesOnly: true,
TeamID: &team1.ID,
}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 2)
for _, title := range titles {
require.NotNil(t, title.SoftwarePackage)
}
})
}