Added available_for_install query parameter to host details (#20876)
Missing part of #19447 Added `available_for_install` query parameter to `GET /api/v1/fleet/hosta/:id/software` # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -2264,14 +2264,20 @@ AND EXISTS (SELECT 1 FROM software s JOIN software_cve scve ON scve.software_id
|
||||
}
|
||||
|
||||
stmt := stmtInstalled
|
||||
if opts.IncludeAvailableForInstall && !opts.VulnerableOnly {
|
||||
if opts.AvailableForInstall || (opts.IncludeAvailableForInstall && !opts.VulnerableOnly) {
|
||||
namedArgs["vpp_apps_platforms"] = []fleet.AppleDevicePlatform{fleet.IOSPlatform, fleet.IPadOSPlatform, fleet.MacOSPlatform}
|
||||
if fleet.IsLinux(host.Platform) {
|
||||
namedArgs["host_compatible_platforms"] = fleet.HostLinuxOSs
|
||||
} else {
|
||||
namedArgs["host_compatible_platforms"] = []string{host.FleetPlatform()}
|
||||
}
|
||||
stmt += ` UNION ` + stmtAvailable
|
||||
if opts.AvailableForInstall {
|
||||
// Only available for install software
|
||||
stmt = stmtAvailable
|
||||
} else {
|
||||
// All software, including available for install
|
||||
stmt += ` UNION ` + stmtAvailable
|
||||
}
|
||||
}
|
||||
|
||||
// must resolve the named bindings here, before adding the searchLike which
|
||||
|
||||
@@ -64,10 +64,12 @@ func TestSoftware(t *testing.T) {
|
||||
{"insertHostSoftwareInstalledPaths", testInsertHostSoftwareInstalledPaths},
|
||||
{"VerifySoftwareChecksum", testVerifySoftwareChecksum},
|
||||
{"ListHostSoftware", testListHostSoftware},
|
||||
{"ListIOSHostSoftware", testListIOSHostSoftware},
|
||||
{"SetHostSoftwareInstallResult", testSetHostSoftwareInstallResult},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
defer TruncateTables(t, ds)
|
||||
c.fn(t, ds)
|
||||
})
|
||||
@@ -3182,7 +3184,15 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
require.Empty(t, sw)
|
||||
require.Equal(t, &fleet.PaginationMetadata{}, meta)
|
||||
|
||||
// available for install only works too
|
||||
opts.AvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sw)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{}, meta)
|
||||
|
||||
// self-service only works too
|
||||
opts.AvailableForInstall = false
|
||||
opts.SelfServiceOnly = true
|
||||
opts.IncludeAvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
@@ -3375,6 +3385,14 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
compareResults(expected, sw, true, byNSV[a2].Name+byNSV[a2].Source, byNSV[c1].Name+byNSV[c1].Source, byNSV[d].Name+byNSV[d].Source)
|
||||
opts.VulnerableOnly = false
|
||||
|
||||
// No software that is available for install
|
||||
opts.AvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sw)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{}, meta)
|
||||
opts.AvailableForInstall = false
|
||||
|
||||
// create some Fleet installers and map them to a software title,
|
||||
// including one for a team
|
||||
tm, err := ds.NewTeam(ctx, &fleet.Team{Name: "team1"})
|
||||
@@ -3564,6 +3582,16 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
require.Equal(t, &fleet.PaginationMetadata{TotalResults: 8}, meta)
|
||||
compareResults(expected, sw, true, i3.Name+i3.Source)
|
||||
|
||||
// request with available software only
|
||||
expectedAvailableOnly := map[string]fleet.HostSoftwareWithInstaller{}
|
||||
expectedAvailableOnly[i2.Name+i2.Source] = i2
|
||||
opts.AvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: 1}, meta)
|
||||
compareResults(expectedAvailableOnly, sw, true)
|
||||
opts.AvailableForInstall = false
|
||||
|
||||
// request in descending order
|
||||
opts.ListOptions.OrderDirection = fleet.OrderDescending
|
||||
opts.ListOptions.TestSecondaryOrderDirection = fleet.OrderDescending
|
||||
@@ -3742,6 +3770,7 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
Status: nil,
|
||||
AppStoreApp: &fleet.SoftwarePackageOrApp{AppStoreID: vpp3},
|
||||
}
|
||||
expectedAvailableOnly["vpp3apps"] = expected["vpp3apps"]
|
||||
opts.IncludeAvailableForInstall = true
|
||||
opts.ListOptions.PerPage = 20
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
@@ -3749,6 +3778,14 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
require.Equal(t, &fleet.PaginationMetadata{TotalResults: 11}, meta)
|
||||
compareResults(expected, sw, true, i3.Name+i3.Source) // i3 is for team
|
||||
|
||||
// Available for install only
|
||||
opts.AvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: 2}, meta)
|
||||
compareResults(expectedAvailableOnly, sw, true)
|
||||
opts.AvailableForInstall = false
|
||||
|
||||
// team host sees available i3 and pending vpp1
|
||||
opts.IncludeAvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, tmHost, opts)
|
||||
@@ -3843,6 +3880,16 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
wantNames: []string{},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 2},
|
||||
},
|
||||
{
|
||||
opts: fleet.HostSoftwareTitleListOptions{ListOptions: fleet.ListOptions{Page: 0, PerPage: 2}, AvailableForInstall: true},
|
||||
wantNames: []string{"i2", "vpp3"},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false, TotalResults: 2},
|
||||
},
|
||||
{
|
||||
opts: fleet.HostSoftwareTitleListOptions{ListOptions: fleet.ListOptions{Page: 1, PerPage: 1}, AvailableForInstall: true},
|
||||
wantNames: []string{"vpp3"},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 2},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("%#v", c.opts), func(t *testing.T) {
|
||||
@@ -3854,18 +3901,303 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
sw, meta, err := ds.ListHostSoftware(ctx, host, c.opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(c.wantNames), len(sw))
|
||||
require.Equal(t, c.wantMeta, meta)
|
||||
|
||||
names := make([]string, 0, len(sw))
|
||||
for _, s := range sw {
|
||||
names = append(names, s.Name)
|
||||
}
|
||||
require.Equal(t, c.wantNames, names)
|
||||
assert.Equal(t, c.wantNames, names)
|
||||
assert.Equal(t, c.wantMeta, meta)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testListIOSHostSoftware(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
host := test.NewHost(t, ds, "host1", "", "host1key", "host1uuid", time.Now(), test.WithPlatform("ios"))
|
||||
nanoEnroll(t, ds, host, false)
|
||||
opts := fleet.HostSoftwareTitleListOptions{ListOptions: fleet.ListOptions{PerPage: 10, IncludeMetadata: true, OrderKey: "name",
|
||||
TestSecondaryOrderKey: "source"}}
|
||||
|
||||
user, err := ds.NewUser(ctx, &fleet.User{
|
||||
Password: []byte("p4ssw0rd.123"),
|
||||
Name: "userIOS",
|
||||
Email: "userIOS@example.com",
|
||||
GlobalRole: ptr.String(fleet.RoleAdmin),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expectStatus := func(s fleet.SoftwareInstallerStatus) *fleet.SoftwareInstallerStatus {
|
||||
return &s
|
||||
}
|
||||
|
||||
// no software yet
|
||||
sw, meta, err := ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sw)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{}, meta)
|
||||
|
||||
// add software to the host
|
||||
software := []fleet.Software{
|
||||
{Name: "a", Version: "0.0.1", Source: "ios_apps"},
|
||||
{Name: "b", Version: "0.0.2", Source: "ios_apps"},
|
||||
{Name: "c", Version: "0.0.3", Source: "ios_apps"},
|
||||
{Name: "c", Version: "0.0.4", Source: "ios_apps"},
|
||||
}
|
||||
byNSV := map[string]fleet.Software{}
|
||||
for _, s := range software {
|
||||
byNSV[s.Name+s.Source+s.Version] = s
|
||||
}
|
||||
|
||||
mutationResults, err := ds.UpdateHostSoftware(ctx, host.ID, software)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, mutationResults.Inserted, len(software))
|
||||
for _, m := range mutationResults.Inserted {
|
||||
s, ok := byNSV[m.Name+m.Source+m.Version]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, m.Name, s.Name, "name")
|
||||
assert.Equal(t, m.Version, s.Version, "version")
|
||||
assert.Equal(t, m.Source, s.Source, "source")
|
||||
assert.Zero(t, s.ID) // not set in the map yet
|
||||
assert.NotZero(t, m.ID)
|
||||
s.ID = m.ID
|
||||
byNSV[s.Name+s.Source+s.Version] = s
|
||||
|
||||
}
|
||||
|
||||
assert.NoError(t, ds.LoadHostSoftware(ctx, host, false))
|
||||
assert.Equal(t, len(host.Software), len(software))
|
||||
for _, hs := range host.Software {
|
||||
s, ok := byNSV[hs.Name+hs.Source+hs.Version]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, hs.Name, s.Name, "name")
|
||||
assert.Equal(t, hs.Version, s.Version, "version")
|
||||
assert.Equal(t, hs.Source, s.Source, "source")
|
||||
assert.Equal(t, hs.ID, s.ID)
|
||||
}
|
||||
|
||||
// shorthand keys for expected software
|
||||
getKey := func(i int) string {
|
||||
return software[i].Name + software[i].Source + software[i].Version
|
||||
}
|
||||
a1 := getKey(0)
|
||||
b := getKey(1)
|
||||
c1 := getKey(2)
|
||||
c2 := getKey(3)
|
||||
|
||||
// add some vulnerabilities
|
||||
vulns := []fleet.SoftwareVulnerability{
|
||||
{SoftwareID: byNSV[a1].ID, CVE: "CVE-a-0001"},
|
||||
{SoftwareID: byNSV[a1].ID, CVE: "CVE-a-0002"},
|
||||
{SoftwareID: byNSV[a1].ID, CVE: "CVE-a-0003"},
|
||||
{SoftwareID: byNSV[b].ID, CVE: "CVE-b-0001"},
|
||||
}
|
||||
for _, v := range vulns {
|
||||
_, err = ds.InsertSoftwareVulnerability(ctx, v, fleet.NVDSource)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = ds.ReconcileSoftwareTitles(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := map[string]fleet.HostSoftwareWithInstaller{
|
||||
byNSV[a1].Name + byNSV[a1].Source: {Name: byNSV[a1].Name, Source: byNSV[a1].Source,
|
||||
InstalledVersions: []*fleet.HostSoftwareInstalledVersion{
|
||||
{Version: byNSV[a1].Version, Vulnerabilities: []string{vulns[0].CVE, vulns[1].CVE, vulns[2].CVE}},
|
||||
}},
|
||||
byNSV[b].Name + byNSV[b].Source: {Name: byNSV[b].Name, Source: byNSV[b].Source,
|
||||
InstalledVersions: []*fleet.HostSoftwareInstalledVersion{
|
||||
{Version: byNSV[b].Version, Vulnerabilities: []string{vulns[3].CVE}},
|
||||
}},
|
||||
// c1 and c2 are the same software title because they have the same name and source
|
||||
byNSV[c1].Name + byNSV[c1].Source: {Name: byNSV[c1].Name, Source: byNSV[c1].Source,
|
||||
InstalledVersions: []*fleet.HostSoftwareInstalledVersion{
|
||||
{Version: byNSV[c1].Version},
|
||||
{Version: byNSV[c2].Version},
|
||||
}},
|
||||
}
|
||||
|
||||
compareResults := func(expected map[string]fleet.HostSoftwareWithInstaller, got []*fleet.HostSoftwareWithInstaller, expectAsc bool,
|
||||
expectOmitted ...string) {
|
||||
require.Len(t, got, len(expected)-len(expectOmitted))
|
||||
prev := ""
|
||||
for _, g := range got {
|
||||
e, ok := expected[g.Name+g.Source]
|
||||
require.True(t, ok, "unexpected software name:%s source:%s", g.Name, g.Source)
|
||||
require.Equal(t, e.Name, g.Name)
|
||||
require.Equal(t, e.Source, g.Source)
|
||||
if e.SoftwarePackage != nil {
|
||||
require.Equal(t, e.SoftwarePackage.SelfService, g.SoftwarePackage.SelfService)
|
||||
require.Equal(t, e.SoftwarePackage.IconURL, g.SoftwarePackage.IconURL)
|
||||
require.Equal(t, e.SoftwarePackage.AppStoreID, g.SoftwarePackage.AppStoreID)
|
||||
require.Equal(t, e.SoftwarePackage.Name, g.SoftwarePackage.Name)
|
||||
require.Equal(t, e.SoftwarePackage.Version, g.SoftwarePackage.Version)
|
||||
if e.SoftwarePackage.LastInstall != nil {
|
||||
require.Equal(t, e.SoftwarePackage.LastInstall.CommandUUID, g.SoftwarePackage.LastInstall.CommandUUID)
|
||||
require.Equal(t, e.SoftwarePackage.LastInstall.InstallUUID, g.SoftwarePackage.LastInstall.InstallUUID)
|
||||
require.NotNil(t, g.SoftwarePackage.LastInstall.InstalledAt)
|
||||
}
|
||||
}
|
||||
|
||||
if e.AppStoreApp != nil {
|
||||
require.Equal(t, e.AppStoreApp.SelfService, g.AppStoreApp.SelfService)
|
||||
require.Equal(t, e.AppStoreApp.IconURL, g.AppStoreApp.IconURL)
|
||||
require.Equal(t, e.AppStoreApp.AppStoreID, g.AppStoreApp.AppStoreID)
|
||||
require.Equal(t, e.AppStoreApp.Name, g.AppStoreApp.Name)
|
||||
require.Equal(t, e.AppStoreApp.Version, g.AppStoreApp.Version)
|
||||
if e.AppStoreApp.LastInstall != nil {
|
||||
require.Equal(t, e.AppStoreApp.LastInstall.InstallUUID, g.AppStoreApp.LastInstall.InstallUUID)
|
||||
require.Equal(t, e.AppStoreApp.LastInstall.CommandUUID, g.AppStoreApp.LastInstall.CommandUUID)
|
||||
require.NotNil(t, g.AppStoreApp.LastInstall.InstalledAt)
|
||||
}
|
||||
}
|
||||
require.Len(t, g.InstalledVersions, len(e.InstalledVersions))
|
||||
if len(e.InstalledVersions) > 0 {
|
||||
byVers := make(map[string]fleet.HostSoftwareInstalledVersion, len(e.InstalledVersions))
|
||||
for _, v := range e.InstalledVersions {
|
||||
byVers[v.Version] = *v
|
||||
}
|
||||
for _, v := range g.InstalledVersions {
|
||||
ev, ok := byVers[v.Version]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, ev.Version, v.Version)
|
||||
require.ElementsMatch(t, ev.InstalledPaths, v.InstalledPaths)
|
||||
require.ElementsMatch(t, ev.Vulnerabilities, v.Vulnerabilities)
|
||||
}
|
||||
}
|
||||
if prev != "" {
|
||||
if expectAsc {
|
||||
require.Greater(t, g.Name+g.Source, prev)
|
||||
} else {
|
||||
require.Less(t, g.Name+g.Source, prev)
|
||||
}
|
||||
}
|
||||
prev = g.Name + g.Source
|
||||
}
|
||||
}
|
||||
|
||||
// it now returns the software with vulnerabilities and installed paths
|
||||
opts.SelfServiceOnly = false
|
||||
opts.IncludeAvailableForInstall = false
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: uint(len(expected))}, meta)
|
||||
compareResults(expected, sw, true)
|
||||
|
||||
opts.VulnerableOnly = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: uint(len(expected) - 1)}, meta)
|
||||
compareResults(expected, sw, true, byNSV[c1].Name+byNSV[c1].Source)
|
||||
opts.VulnerableOnly = false
|
||||
|
||||
// No software that is available for install
|
||||
opts.AvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sw)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{}, meta)
|
||||
opts.AvailableForInstall = false
|
||||
|
||||
// Create a team
|
||||
tm, err := ds.NewTeam(ctx, &fleet.Team{Name: "mobile team"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// add VPP apps, one for both no team and team, and three for no-team only.
|
||||
va1, err := ds.InsertVPPAppWithTeam(ctx,
|
||||
&fleet.VPPApp{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_1", Platform: fleet.IOSPlatform}, Name: "vpp1",
|
||||
BundleIdentifier: "com.app.vpp1"}, nil)
|
||||
require.NoError(t, err)
|
||||
_, err = ds.InsertVPPAppWithTeam(ctx,
|
||||
&fleet.VPPApp{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_1", Platform: fleet.MacOSPlatform}, Name: "vpp1",
|
||||
BundleIdentifier: "com.app.vpp1"}, nil)
|
||||
require.NoError(t, err)
|
||||
_, err = ds.InsertVPPAppWithTeam(ctx,
|
||||
&fleet.VPPApp{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_1", Platform: fleet.IPadOSPlatform}, Name: "vpp1",
|
||||
BundleIdentifier: "com.app.vpp1"}, nil)
|
||||
require.NoError(t, err)
|
||||
_, err = ds.InsertVPPAppWithTeam(ctx,
|
||||
&fleet.VPPApp{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_1", Platform: fleet.IPadOSPlatform}, Name: "vpp1",
|
||||
BundleIdentifier: "com.app.vpp1"}, &tm.ID)
|
||||
require.NoError(t, err)
|
||||
vpp1 := va1.AdamID
|
||||
va2, err := ds.InsertVPPAppWithTeam(ctx,
|
||||
&fleet.VPPApp{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_2", Platform: fleet.IOSPlatform}, Name: "vpp2",
|
||||
BundleIdentifier: "com.app.vpp2"}, nil)
|
||||
require.NoError(t, err)
|
||||
va3, err := ds.InsertVPPAppWithTeam(ctx,
|
||||
&fleet.VPPApp{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_3", Platform: fleet.IOSPlatform}, Name: "vpp3",
|
||||
BundleIdentifier: "com.app.vpp3"}, nil)
|
||||
require.NoError(t, err)
|
||||
va4, err := ds.InsertVPPAppWithTeam(ctx,
|
||||
&fleet.VPPApp{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_4", Platform: fleet.IOSPlatform}, Name: "vpp4",
|
||||
BundleIdentifier: "com.app.vpp4"}, nil)
|
||||
require.NoError(t, err)
|
||||
vpp2, vpp3, vpp4 := va2.AdamID, va3.AdamID, va4.AdamID
|
||||
|
||||
// create an installation request for vpp1 and vpp2, leaving vpp3 and vpp4 as
|
||||
// available only
|
||||
vpp1CmdUUID := createVPPAppInstallRequest(t, ds, host, vpp1, user.ID)
|
||||
vpp2CmdUUID := createVPPAppInstallRequest(t, ds, host, vpp2, user.ID)
|
||||
// make vpp1 install a success, while vpp2 has its initial request as failed
|
||||
// and a subsequent request as pending.
|
||||
createVPPAppInstallResult(t, ds, host, vpp1CmdUUID, fleet.MDMAppleStatusAcknowledged)
|
||||
createVPPAppInstallResult(t, ds, host, vpp2CmdUUID, fleet.MDMAppleStatusError)
|
||||
time.Sleep(time.Second) // ensure a different created_at timestamp
|
||||
vpp2bCmdUUID := createVPPAppInstallRequest(t, ds, host, vpp2, user.ID)
|
||||
require.NotEmpty(t, vpp2bCmdUUID)
|
||||
|
||||
expected["vpp1ios_apps"] = fleet.HostSoftwareWithInstaller{
|
||||
Name: "vpp1",
|
||||
Source: "ios_apps",
|
||||
Status: expectStatus(fleet.SoftwareInstallerInstalled),
|
||||
AppStoreApp: &fleet.SoftwarePackageOrApp{AppStoreID: vpp1, LastInstall: &fleet.HostSoftwareInstall{CommandUUID: vpp1CmdUUID}},
|
||||
}
|
||||
expected["vpp2ios_apps"] = fleet.HostSoftwareWithInstaller{
|
||||
Name: "vpp2",
|
||||
Source: "ios_apps",
|
||||
Status: expectStatus(fleet.SoftwareInstallerPending),
|
||||
AppStoreApp: &fleet.SoftwarePackageOrApp{AppStoreID: vpp2, LastInstall: &fleet.HostSoftwareInstall{CommandUUID: vpp2bCmdUUID}},
|
||||
}
|
||||
|
||||
opts.IncludeAvailableForInstall = false
|
||||
opts.ListOptions.MatchQuery = ""
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: uint(len(expected))}, meta)
|
||||
compareResults(expected, sw, true) // i3 is for team, i2 is available (excluded)
|
||||
|
||||
expected["vpp3ios_apps"] = fleet.HostSoftwareWithInstaller{
|
||||
Name: "vpp3",
|
||||
Source: "ios_apps",
|
||||
Status: nil,
|
||||
AppStoreApp: &fleet.SoftwarePackageOrApp{AppStoreID: vpp3},
|
||||
}
|
||||
expected["vpp4ios_apps"] = fleet.HostSoftwareWithInstaller{
|
||||
Name: "vpp4",
|
||||
Source: "ios_apps",
|
||||
Status: nil,
|
||||
AppStoreApp: &fleet.SoftwarePackageOrApp{AppStoreID: vpp4},
|
||||
}
|
||||
expectedAvailableOnly := map[string]fleet.HostSoftwareWithInstaller{}
|
||||
expectedAvailableOnly["vpp3ios_apps"] = expected["vpp3ios_apps"]
|
||||
expectedAvailableOnly["vpp4ios_apps"] = expected["vpp4ios_apps"]
|
||||
opts.IncludeAvailableForInstall = true
|
||||
opts.ListOptions.PerPage = 20
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: uint(len(expected))}, meta)
|
||||
compareResults(expected, sw, true)
|
||||
|
||||
// Available for install only
|
||||
opts.AvailableForInstall = true
|
||||
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: uint(len(expectedAvailableOnly))}, meta)
|
||||
compareResults(expectedAvailableOnly, sw, true)
|
||||
opts.AvailableForInstall = false
|
||||
|
||||
}
|
||||
|
||||
func testSetHostSoftwareInstallResult(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
host := test.NewHost(t, ds, "host1", "", "host1key", "host1uuid", time.Now())
|
||||
|
||||
@@ -240,6 +240,10 @@ type HostSoftwareTitleListOptions struct {
|
||||
// install (but not currently installed on the host) should be returned.
|
||||
IncludeAvailableForInstall bool
|
||||
|
||||
// AvailableForInstall is a query argument that limits the returned software
|
||||
// titles to those that are available for install on the host.
|
||||
AvailableForInstall bool `query:"available_for_install,optional"`
|
||||
|
||||
VulnerableOnly bool `query:"vulnerable,optional"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user