Add uninstalled but available software installers to the "List software titles" API (#18842)

This commit is contained in:
Martin Angers
2024-05-08 12:41:57 -04:00
committed by GitHub
parent c88a7cf6b0
commit 8367166278
4 changed files with 254 additions and 61 deletions
@@ -0,0 +1 @@
* Added the uninstalled but available software installers to the response payload of the "List software titles" endpoint (`GET /software/titles`).
@@ -5,6 +5,7 @@ package scripts
import (
"context"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
@@ -59,6 +60,12 @@ func TestExecCmdNonWindows(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if strings.HasPrefix(tc.contents, "#!"+zshPath) {
// skip if zsh is not installed
if _, err := exec.LookPath(zshPath); err != nil {
t.Skipf("zsh not installed: %s", err)
}
}
scriptPath := strings.ReplaceAll(tc.name, " ", "_") + ".sh"
scriptPath = filepath.Join(tmpDir, scriptPath)
err := os.WriteFile(scriptPath, []byte(tc.contents), os.ModePerm)
+19 -9
View File
@@ -191,15 +191,18 @@ SELECT
st.name,
st.source,
st.browser,
MAX(sthc.hosts_count) as hosts_count,
MAX(sthc.updated_at) as counts_updated_at
MAX(COALESCE(sthc.hosts_count, 0)) as hosts_count,
MAX(COALESCE(sthc.updated_at, date('0001-01-01 00:00:00'))) as counts_updated_at
FROM software_titles st
JOIN software_titles_host_counts sthc ON sthc.software_title_id = st.id
LEFT JOIN software_titles_host_counts sthc ON sthc.software_title_id = st.id AND sthc.team_id = ?
-- placeholder for JOIN on software/software_cve
%s
-- placeholder for optional extra WHERE filter
WHERE sthc.team_id = ? %s
AND sthc.hosts_count > 0
WHERE %s
AND (
sthc.hosts_count > 0 OR
EXISTS (SELECT 1 FROM software_installers si WHERE si.title_id = st.id AND si.global_or_team_id = ?)
)
GROUP BY st.id`
cveJoinType := "LEFT"
@@ -207,27 +210,34 @@ GROUP BY st.id`
cveJoinType = "INNER"
}
var globalOrTeamID uint
args := []any{0}
if opt.TeamID != nil {
args[0] = *opt.TeamID
globalOrTeamID = *opt.TeamID
}
additionalWhere := ""
additionalWhere := "TRUE"
match := opt.ListOptions.MatchQuery
softwareJoin := ""
if match != "" || opt.VulnerableOnly {
// if we do a match but not vulnerable only, we want a LEFT JOIN on
// software because software installers may not have entries in software
// for their software title. If we do want vulnerable only, then we have to
// INNER JOIN because a CVE implies a specific software version.
softwareJoin = fmt.Sprintf(`
JOIN software s ON s.title_id = st.id
%s JOIN software s ON s.title_id = st.id
-- placeholder for changing the JOIN type to filter vulnerable software
%s JOIN software_cve scve ON s.id = scve.software_id
%[1]s JOIN software_cve scve ON s.id = scve.software_id
`, cveJoinType)
}
if match != "" {
additionalWhere += " AND (st.name LIKE ? OR scve.cve LIKE ?)"
additionalWhere = " (st.name LIKE ? OR scve.cve LIKE ?)"
match = likePattern(match)
args = append(args, match, match)
}
args = append(args, globalOrTeamID)
stmt = fmt.Sprintf(stmt, softwareJoin, additionalWhere)
return stmt, args
+227 -52
View File
@@ -3,11 +3,12 @@ package mysql
import (
"context"
"database/sql"
"github.com/stretchr/testify/assert"
"sort"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/test"
@@ -24,6 +25,7 @@ func TestSoftwareTitles(t *testing.T) {
{"SyncHostsSoftwareTitles", testSoftwareSyncHostsSoftwareTitles},
{"OrderSoftwareTitles", testOrderSoftwareTitles},
{"TeamFilterSoftwareTitles", testTeamFilterSoftwareTitles},
{"ListSoftwareTitlesInstallersOnly", testListSoftwareTitlesInstallersOnly},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@@ -70,8 +72,8 @@ func testSoftwareSyncHostsSoftwareTitles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
_, err = ds.UpdateHostSoftware(ctx, host2.ID, software2)
require.NoError(t, err)
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
globalOpts := fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{OrderKey: "hosts_count", OrderDirection: fleet.OrderDescending}}
@@ -91,8 +93,8 @@ func testSoftwareSyncHostsSoftwareTitles(t *testing.T, ds *Datastore) {
}
_, err = ds.UpdateHostSoftware(ctx, host2.ID, software2)
require.NoError(t, err)
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
globalCounts = listSoftwareTitlesCheckCount(t, ds, 1, 1, globalOpts, false)
@@ -157,8 +159,8 @@ func testSoftwareSyncHostsSoftwareTitles(t *testing.T, ds *Datastore) {
checkTableTotalCount(1)
// after a call to Calculate, the global counts are updated and the team counts appear
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
globalCounts = listSoftwareTitlesCheckCount(t, ds, 2, 2, globalOpts, false)
@@ -195,8 +197,8 @@ func testSoftwareSyncHostsSoftwareTitles(t *testing.T, ds *Datastore) {
_, err = ds.UpdateHostSoftware(ctx, host4.ID, software4)
require.NoError(t, err)
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
globalCounts = listSoftwareTitlesCheckCount(t, ds, 1, 1, globalOpts, false)
@@ -223,8 +225,8 @@ func testSoftwareSyncHostsSoftwareTitles(t *testing.T, ds *Datastore) {
software4 = []fleet.Software{}
_, err = ds.UpdateHostSoftware(ctx, host4.ID, software4)
require.NoError(t, err)
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
listSoftwareTitlesCheckCount(t, ds, 0, 0, team2Opts, false)
@@ -232,8 +234,8 @@ func testSoftwareSyncHostsSoftwareTitles(t *testing.T, ds *Datastore) {
require.NoError(t, ds.DeleteTeam(ctx, team2.ID))
// this call will remove team2 from the software host counts table
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
globalCounts = listSoftwareTitlesCheckCount(t, ds, 1, 1, globalOpts, false)
@@ -284,8 +286,29 @@ func testOrderSoftwareTitles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
_, err = ds.UpdateHostSoftware(ctx, host3.ID, software3)
require.NoError(t, err)
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
// create a software installer not installed on any host
installer1, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "installer1",
Source: "apps",
InstallScript: "echo",
Filename: "installer1.pkg",
})
require.NoError(t, err)
require.NotZero(t, installer1)
// create a software installer with an install request on host1
installer2, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "installer2",
Source: "apps",
InstallScript: "echo",
Filename: "installer2.pkg",
})
require.NoError(t, err)
err = ds.InsertSoftwareInstallRequest(ctx, host1.ID, installer2)
require.NoError(t, err)
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
// primary sort is "hosts_count DESC", followed by "name ASC, source ASC, browser ASC"
@@ -294,7 +317,7 @@ func testOrderSoftwareTitles(t *testing.T, ds *Datastore) {
OrderDirection: fleet.OrderDescending,
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 7)
require.Len(t, titles, 9)
require.Equal(t, "bar", titles[0].Name)
require.Equal(t, "deb_packages", titles[0].Source)
require.Equal(t, "foo", titles[1].Name)
@@ -311,6 +334,10 @@ func testOrderSoftwareTitles(t *testing.T, ds *Datastore) {
require.Equal(t, "edge", titles[5].Browser)
require.Equal(t, "foo", titles[6].Name)
require.Equal(t, "rpm_packages", titles[6].Source)
require.Equal(t, "installer1", titles[7].Name)
require.Equal(t, "apps", titles[7].Source)
require.Equal(t, "installer2", titles[8].Name)
require.Equal(t, "apps", titles[8].Source)
// primary sort is "hosts_count ASC", followed by "name ASC, source ASC, browser ASC"
titles, _, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
@@ -318,23 +345,27 @@ func testOrderSoftwareTitles(t *testing.T, ds *Datastore) {
OrderDirection: fleet.OrderAscending,
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 7)
require.Equal(t, "bar", titles[0].Name)
require.Len(t, titles, 9)
require.Equal(t, "installer1", titles[0].Name)
require.Equal(t, "apps", titles[0].Source)
require.Equal(t, "baz", titles[1].Name)
require.Equal(t, "chrome_extensions", titles[1].Source)
require.Equal(t, "chrome", titles[1].Browser)
require.Equal(t, "baz", titles[2].Name)
require.Equal(t, "chrome_extensions", titles[2].Source)
require.Equal(t, "edge", titles[2].Browser)
require.Equal(t, "foo", titles[3].Name)
require.Equal(t, "rpm_packages", titles[3].Source)
require.Equal(t, "bar", titles[4].Name)
require.Equal(t, "deb_packages", titles[4].Source)
require.Equal(t, "installer2", titles[1].Name)
require.Equal(t, "apps", titles[1].Source)
require.Equal(t, "bar", titles[2].Name)
require.Equal(t, "apps", titles[2].Source)
require.Equal(t, "baz", titles[3].Name)
require.Equal(t, "chrome_extensions", titles[3].Source)
require.Equal(t, "chrome", titles[3].Browser)
require.Equal(t, "baz", titles[4].Name)
require.Equal(t, "chrome_extensions", titles[4].Source)
require.Equal(t, "edge", titles[4].Browser)
require.Equal(t, "foo", titles[5].Name)
require.Equal(t, "chrome_extensions", titles[5].Source)
require.Equal(t, "foo", titles[6].Name)
require.Equal(t, "rpm_packages", titles[5].Source)
require.Equal(t, "bar", titles[6].Name)
require.Equal(t, "deb_packages", titles[6].Source)
require.Equal(t, "foo", titles[7].Name)
require.Equal(t, "chrome_extensions", titles[7].Source)
require.Equal(t, "foo", titles[8].Name)
require.Equal(t, "deb_packages", titles[8].Source)
// primary sort is "name ASC", followed by "host_count DESC, source ASC, browser ASC"
titles, _, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
@@ -342,7 +373,7 @@ func testOrderSoftwareTitles(t *testing.T, ds *Datastore) {
OrderDirection: fleet.OrderAscending,
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 7)
require.Len(t, titles, 9)
require.Equal(t, "bar", titles[0].Name)
require.Equal(t, "deb_packages", titles[0].Source)
require.Equal(t, "bar", titles[1].Name)
@@ -359,6 +390,10 @@ func testOrderSoftwareTitles(t *testing.T, ds *Datastore) {
require.Equal(t, "deb_packages", titles[5].Source)
require.Equal(t, "foo", titles[6].Name)
require.Equal(t, "rpm_packages", titles[6].Source)
require.Equal(t, "installer1", titles[7].Name)
require.Equal(t, "apps", titles[7].Source)
require.Equal(t, "installer2", titles[8].Name)
require.Equal(t, "apps", titles[8].Source)
// primary sort is "name DESC", followed by "host_count DESC, source ASC, browser ASC"
titles, _, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
@@ -366,23 +401,59 @@ func testOrderSoftwareTitles(t *testing.T, ds *Datastore) {
OrderDirection: fleet.OrderDescending,
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 7)
require.Equal(t, "foo", titles[0].Name)
require.Equal(t, "chrome_extensions", titles[0].Source)
require.Equal(t, "foo", titles[1].Name)
require.Equal(t, "deb_packages", titles[1].Source)
require.Len(t, titles, 9)
require.Equal(t, "installer2", titles[0].Name)
require.Equal(t, "apps", titles[0].Source)
require.Equal(t, "installer1", titles[1].Name)
require.Equal(t, "apps", titles[1].Source)
require.Equal(t, "foo", titles[2].Name)
require.Equal(t, "rpm_packages", titles[2].Source)
require.Equal(t, "baz", titles[3].Name)
require.Equal(t, "chrome_extensions", titles[3].Source)
require.Equal(t, "chrome", titles[3].Browser)
require.Equal(t, "baz", titles[4].Name)
require.Equal(t, "chrome_extensions", titles[4].Source)
require.Equal(t, "edge", titles[4].Browser)
require.Equal(t, "bar", titles[5].Name)
require.Equal(t, "deb_packages", titles[5].Source)
require.Equal(t, "bar", titles[6].Name)
require.Equal(t, "apps", titles[6].Source)
require.Equal(t, "chrome_extensions", titles[2].Source)
require.Equal(t, "foo", titles[3].Name)
require.Equal(t, "deb_packages", titles[3].Source)
require.Equal(t, "foo", titles[4].Name)
require.Equal(t, "rpm_packages", titles[4].Source)
require.Equal(t, "baz", titles[5].Name)
require.Equal(t, "chrome_extensions", titles[5].Source)
require.Equal(t, "chrome", titles[5].Browser)
require.Equal(t, "baz", titles[6].Name)
require.Equal(t, "chrome_extensions", titles[6].Source)
require.Equal(t, "edge", titles[6].Browser)
require.Equal(t, "bar", titles[7].Name)
require.Equal(t, "deb_packages", titles[7].Source)
require.Equal(t, "bar", titles[8].Name)
require.Equal(t, "apps", titles[8].Source)
// using a match query
titles, _, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
OrderKey: "name",
OrderDirection: fleet.OrderDescending,
MatchQuery: "ba",
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 4)
require.Equal(t, "baz", titles[0].Name)
require.Equal(t, "chrome_extensions", titles[0].Source)
require.Equal(t, "chrome", titles[0].Browser)
require.Equal(t, "baz", titles[1].Name)
require.Equal(t, "chrome_extensions", titles[1].Source)
require.Equal(t, "edge", titles[1].Browser)
require.Equal(t, "bar", titles[2].Name)
require.Equal(t, "deb_packages", titles[2].Source)
require.Equal(t, "bar", titles[3].Name)
require.Equal(t, "apps", titles[3].Source)
// using another (installer-only) match query
titles, _, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
OrderKey: "name",
OrderDirection: fleet.OrderDescending,
MatchQuery: "insta",
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.Len(t, titles, 2)
require.Equal(t, "installer2", titles[0].Name)
require.Equal(t, "apps", titles[0].Source)
require.Equal(t, "installer1", titles[1].Name)
require.Equal(t, "apps", titles[1].Source)
}
func listSoftwareTitlesCheckCount(t *testing.T, ds *Datastore, expectedListCount int, expectedFullCount int, opts fleet.SoftwareTitleListOptions, returnSorted bool) []fleet.SoftwareTitle {
@@ -407,11 +478,11 @@ func testTeamFilterSoftwareTitles(t *testing.T, ds *Datastore) {
host2 := test.NewHost(t, ds, "host2", "", "host2key", "host2uuid", time.Now())
require.NoError(t, ds.AddHostsToTeam(ctx, &team2.ID, []uint{host2.ID}))
user1, err := ds.NewUser(ctx, &fleet.User{Name: "user1", Password: []byte("test"), Email: "test1@email.com", GlobalRole: ptr.String(fleet.RoleAdmin)})
userGlobalAdmin, err := ds.NewUser(ctx, &fleet.User{Name: "user1", Password: []byte("test"), Email: "test1@email.com", GlobalRole: ptr.String(fleet.RoleAdmin)})
require.NoError(t, err)
user2, err := ds.NewUser(ctx, &fleet.User{Name: "user2", Password: []byte("test"), Email: "test2@email.com", Teams: []fleet.UserTeam{{Team: fleet.Team{ID: team1.ID}, Role: fleet.RoleAdmin}}})
userTeam1Admin, err := ds.NewUser(ctx, &fleet.User{Name: "user2", Password: []byte("test"), Email: "test2@email.com", Teams: []fleet.UserTeam{{Team: fleet.Team{ID: team1.ID}, Role: fleet.RoleAdmin}}})
require.NoError(t, err)
user3, err := ds.NewUser(ctx, &fleet.User{Name: "user3", Password: []byte("test"), Email: "test3@email.com", Teams: []fleet.UserTeam{{Team: fleet.Team{ID: team2.ID}, Role: fleet.RoleAdmin}}})
userTeam2Admin, err := ds.NewUser(ctx, &fleet.User{Name: "user3", Password: []byte("test"), Email: "test3@email.com", Teams: []fleet.UserTeam{{Team: fleet.Team{ID: team2.ID}, Role: fleet.RoleAdmin}}})
require.NoError(t, err)
software1 := []fleet.Software{
@@ -427,19 +498,47 @@ func testTeamFilterSoftwareTitles(t *testing.T, ds *Datastore) {
_, err = ds.UpdateHostSoftware(ctx, host2.ID, software2)
require.NoError(t, err)
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
// create a software installer for team1
installer1, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "installer1",
Source: "apps",
InstallScript: "echo",
Filename: "installer1.pkg",
TeamID: &team1.ID,
})
require.NoError(t, err)
require.NotZero(t, installer1)
// create a software installer for team2
installer2, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "installer2",
Source: "apps",
InstallScript: "echo",
Filename: "installer2.pkg",
TeamID: &team2.ID,
})
require.NoError(t, err)
require.NotZero(t, installer2)
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
// Testing the global user
globalTeamFilter := fleet.TeamFilter{User: user1, IncludeObserver: true}
// Testing the global user (for no team)
globalTeamFilter := fleet.TeamFilter{User: userGlobalAdmin, IncludeObserver: true}
titles, count, _, err := ds.ListSoftwareTitles(
context.Background(), fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{}}, globalTeamFilter,
)
sortTitlesByName(titles)
// software installers are associated with a team, so they don't show up in
// this request for no team, but other titles do because software titles are
// not associated with a team.
require.NoError(t, err)
require.Len(t, titles, 2)
require.Equal(t, 2, count)
require.Equal(t, "bar", titles[0].Name)
require.Equal(t, "deb_packages", titles[0].Source)
require.Equal(t, "foo", titles[1].Name)
require.Equal(t, "chrome_extensions", titles[1].Source)
require.Equal(t, uint(1), titles[0].VersionsCount)
assert.Equal(t, uint(1), titles[0].HostsCount)
require.Equal(t, uint(2), titles[1].VersionsCount)
@@ -465,14 +564,20 @@ func testTeamFilterSoftwareTitles(t *testing.T, ds *Datastore) {
assert.Equal(t, "0.0.3", title.Versions[0].Version)
// Testing the team 1 user
team1TeamFilter := fleet.TeamFilter{User: user2, IncludeObserver: true}
team1TeamFilter := fleet.TeamFilter{User: userTeam1Admin, IncludeObserver: true}
titles, count, _, err = ds.ListSoftwareTitles(
context.Background(), fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{}, TeamID: &team1.ID}, team1TeamFilter,
)
// installer1 is associated with team 1
require.NoError(t, err)
require.Len(t, titles, 1)
require.Equal(t, 1, count)
require.Len(t, titles, 2)
require.Equal(t, 2, count)
require.Equal(t, "foo", titles[0].Name)
require.Equal(t, "chrome_extensions", titles[0].Source)
require.Equal(t, "installer1", titles[1].Name)
require.Equal(t, "apps", titles[1].Source)
require.Equal(t, uint(1), titles[0].VersionsCount)
require.Equal(t, uint(0), titles[1].VersionsCount)
// Testing with team filter -- this team does contain this software title
title, err = ds.SoftwareTitleByID(context.Background(), titles[0].ID, &team1.ID, team1TeamFilter)
@@ -483,16 +588,86 @@ func testTeamFilterSoftwareTitles(t *testing.T, ds *Datastore) {
// Testing the team 2 user
titles, count, _, err = ds.ListSoftwareTitles(context.Background(), fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{}, TeamID: &team2.ID}, fleet.TeamFilter{
User: user3,
User: userTeam2Admin,
IncludeObserver: true,
})
// installer2 is associated with team 2
require.NoError(t, err)
require.Len(t, titles, 2)
require.Equal(t, 2, count)
require.Len(t, titles, 3)
require.Equal(t, 3, count)
require.Equal(t, "bar", titles[0].Name)
require.Equal(t, "deb_packages", titles[0].Source)
require.Equal(t, "foo", titles[1].Name)
require.Equal(t, "chrome_extensions", titles[1].Source)
require.Equal(t, "installer2", titles[2].Name)
require.Equal(t, "apps", titles[2].Source)
require.Equal(t, uint(1), titles[0].VersionsCount)
require.Equal(t, uint(1), titles[1].VersionsCount)
require.Equal(t, uint(0), titles[2].VersionsCount)
}
func sortTitlesByName(titles []fleet.SoftwareTitle) {
sort.Slice(titles, func(i, j int) bool { return titles[i].Name < titles[j].Name })
}
func testListSoftwareTitlesInstallersOnly(t *testing.T, ds *Datastore) {
ctx := context.Background()
// create a couple software installers not installed on any host
installer1, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "installer1",
Source: "apps",
InstallScript: "echo",
Filename: "installer1.pkg",
})
require.NoError(t, err)
require.NotZero(t, installer1)
installer2, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
Title: "installer2",
Source: "apps",
InstallScript: "echo",
Filename: "installer2.pkg",
})
require.NoError(t, err)
require.NotZero(t, installer2)
titles, counts, _, err := ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
OrderKey: "name",
OrderDirection: fleet.OrderAscending,
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.EqualValues(t, 2, counts)
require.Len(t, titles, 2)
require.Equal(t, "installer1", titles[0].Name)
require.Equal(t, "apps", titles[0].Source)
require.Equal(t, "installer2", titles[1].Name)
require.Equal(t, "apps", titles[1].Source)
require.True(t, titles[0].CountsUpdatedAt.IsZero())
require.True(t, titles[1].CountsUpdatedAt.IsZero())
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
titles, counts, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
OrderKey: "name",
OrderDirection: fleet.OrderAscending,
MatchQuery: "installer1",
}}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.EqualValues(t, 1, counts)
require.Len(t, titles, 1)
require.Equal(t, "installer1", titles[0].Name)
require.Equal(t, "apps", titles[0].Source)
require.True(t, titles[0].CountsUpdatedAt.IsZero())
// vulnerable only returns nothing
titles, counts, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{
OrderKey: "name",
OrderDirection: fleet.OrderAscending,
MatchQuery: "installer1",
}, VulnerableOnly: true}, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
require.NoError(t, err)
require.EqualValues(t, 0, counts)
require.Len(t, titles, 0)
}