Bugfix: list host software "Available for install" filter must show installers that have an install request on the host (#21083)

This commit is contained in:
Martin Angers
2024-08-06 10:14:01 -04:00
committed by GitHub
parent 4853ecbf4e
commit ef07a406cc
4 changed files with 60 additions and 47 deletions
+25 -23
View File
@@ -2101,6 +2101,21 @@ AND EXISTS (SELECT 1 FROM software s JOIN software_cve scve ON scve.software_id
`
}
var softwareIsInstalledOnHostClause string
if !opts.OnlyAvailableForInstall {
softwareIsInstalledOnHostClause = `
EXISTS (
SELECT 1
FROM
host_software hs
INNER JOIN
software s ON hs.software_id = s.id
WHERE
hs.host_id = :host_id AND
s.title_id = st.id
) OR `
}
// this statement lists only the software that is reported as installed on
// the host or has been attempted to be installed on the host.
stmtInstalled := fmt.Sprintf(`
@@ -2133,7 +2148,7 @@ AND EXISTS (SELECT 1 FROM software s JOIN software_cve scve ON scve.software_id
LEFT OUTER JOIN
nano_command_results ncr ON ncr.command_uuid = hvsi.command_uuid
WHERE
-- use the latest install only
-- use the latest install attempt only
( hsi.id IS NULL OR hsi.id = (
SELECT hsi2.id
FROM host_software_installs hsi2
@@ -2146,22 +2161,15 @@ AND EXISTS (SELECT 1 FROM software s JOIN software_cve scve ON scve.software_id
WHERE hvsi2.host_id = hvsi.host_id AND hvsi2.adam_id = hvsi.adam_id AND hvsi2.platform = hvsi.platform
ORDER BY hvsi2.created_at DESC
LIMIT 1 ) ) AND
-- software is installed on host
( EXISTS (
SELECT 1
FROM
host_software hs
INNER JOIN
software s ON hs.software_id = s.id
WHERE
hs.host_id = :host_id AND
s.title_id = st.id
) OR
-- or software install has been attempted on host (via installer or VPP app)
hsi.host_id IS NOT NULL OR hvsi.host_id IS NOT NULL )
-- software is installed on host or software install has been attempted
-- on host (via installer or VPP app). If only available for install is
-- requested, then the software installed on host clause is empty.
( %s hsi.host_id IS NOT NULL OR hvsi.host_id IS NOT NULL )
%s
%s
`, softwareInstallerHostStatusNamedQuery("hsi", ""), vppAppHostStatusNamedQuery("hvsi", "ncr", ""), onlySelfServiceClause, onlyVulnerableClause)
`, softwareInstallerHostStatusNamedQuery("hsi", ""), vppAppHostStatusNamedQuery("hvsi", "ncr", ""),
softwareIsInstalledOnHostClause, onlySelfServiceClause, onlyVulnerableClause)
// this statement lists only the software that has never been installed nor
// attempted to be installed on the host, but that is available to be
@@ -2262,20 +2270,14 @@ AND EXISTS (SELECT 1 FROM software s JOIN software_cve scve ON scve.software_id
}
stmt := stmtInstalled
if opts.AvailableForInstall || (opts.IncludeAvailableForInstall && !opts.VulnerableOnly) {
if opts.OnlyAvailableForInstall || (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()}
}
if opts.AvailableForInstall {
// Only available for install software
stmt = stmtAvailable
} else {
// All software, including available for install
stmt += ` UNION ` + stmtAvailable
}
stmt += ` UNION ` + stmtAvailable
}
// must resolve the named bindings here, before adding the searchLike which
+30 -21
View File
@@ -3185,14 +3185,14 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
require.Equal(t, &fleet.PaginationMetadata{}, meta)
// available for install only works too
opts.AvailableForInstall = true
opts.OnlyAvailableForInstall = 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.OnlyAvailableForInstall = false
opts.SelfServiceOnly = true
opts.IncludeAvailableForInstall = true
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
@@ -3386,12 +3386,12 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
opts.VulnerableOnly = false
// No software that is available for install
opts.AvailableForInstall = true
opts.OnlyAvailableForInstall = 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
opts.OnlyAvailableForInstall = false
// create some Fleet installers and map them to a software title,
// including one for a team
@@ -3582,15 +3582,18 @@ 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
// request with available software only (attempted to install and never attempted to install)
expectedAvailableOnly := map[string]fleet.HostSoftwareWithInstaller{}
expectedAvailableOnly[byNSV[b].Name+byNSV[b].Source] = expected[byNSV[b].Name+byNSV[b].Source]
expectedAvailableOnly[i0.Name+i0.Source] = i0
expectedAvailableOnly[i1.Name+i1.Source] = i1
expectedAvailableOnly[i2.Name+i2.Source] = i2
opts.AvailableForInstall = true
opts.OnlyAvailableForInstall = true
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
require.NoError(t, err)
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: 1}, meta)
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: uint(len(expectedAvailableOnly))}, meta)
compareResults(expectedAvailableOnly, sw, true)
opts.AvailableForInstall = false
opts.OnlyAvailableForInstall = false
// request in descending order
opts.ListOptions.OrderDirection = fleet.OrderDescending
@@ -3639,6 +3642,8 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
Status: expectStatus(fleet.SoftwareInstallerPending),
SoftwarePackage: &fleet.SoftwarePackageOrApp{Name: "installer-2.pkg", Version: "v2.0.0", SelfService: ptr.Bool(false), LastInstall: &fleet.HostSoftwareInstall{InstallUUID: "uuid4"}},
}
expectedAvailableOnly[byNSV[b].Name+byNSV[b].Source] = expected[byNSV[b].Name+byNSV[b].Source]
expectedAvailableOnly[i1.Name+i1.Source] = expected[i1.Name+i1.Source]
// request without available software
opts.IncludeAvailableForInstall = false
@@ -3770,6 +3775,8 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
Status: nil,
AppStoreApp: &fleet.SoftwarePackageOrApp{AppStoreID: vpp3},
}
expectedAvailableOnly["vpp1apps"] = expected["vpp1apps"]
expectedAvailableOnly["vpp2apps"] = expected["vpp2apps"]
expectedAvailableOnly["vpp3apps"] = expected["vpp3apps"]
opts.IncludeAvailableForInstall = true
opts.ListOptions.PerPage = 20
@@ -3779,12 +3786,12 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
compareResults(expected, sw, true, i3.Name+i3.Source) // i3 is for team
// Available for install only
opts.AvailableForInstall = true
opts.OnlyAvailableForInstall = true
sw, meta, err = ds.ListHostSoftware(ctx, host, opts)
require.NoError(t, err)
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: 2}, meta)
assert.Equal(t, &fleet.PaginationMetadata{TotalResults: uint(len(expectedAvailableOnly))}, meta)
compareResults(expectedAvailableOnly, sw, true)
opts.AvailableForInstall = false
opts.OnlyAvailableForInstall = false
// team host sees available i3 and pending vpp1
opts.IncludeAvailableForInstall = true
@@ -3881,14 +3888,14 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
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: 0, PerPage: 4}, OnlyAvailableForInstall: true},
wantNames: []string{byNSV[b].Name, "i0", "i1", "i2"},
wantMeta: &fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: false, TotalResults: 7},
},
{
opts: fleet.HostSoftwareTitleListOptions{ListOptions: fleet.ListOptions{Page: 1, PerPage: 1}, AvailableForInstall: true},
wantNames: []string{"vpp3"},
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 2},
opts: fleet.HostSoftwareTitleListOptions{ListOptions: fleet.ListOptions{Page: 1, PerPage: 4}, OnlyAvailableForInstall: true},
wantNames: []string{"vpp1", "vpp2", "vpp3"},
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 7},
},
}
for _, c := range cases {
@@ -4091,12 +4098,12 @@ func testListIOSHostSoftware(t *testing.T, ds *Datastore) {
opts.VulnerableOnly = false
// No software that is available for install
opts.AvailableForInstall = true
opts.OnlyAvailableForInstall = 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
opts.OnlyAvailableForInstall = false
// Create a team
tm, err := ds.NewTeam(ctx, &fleet.Team{Name: "mobile team"})
@@ -4179,6 +4186,8 @@ func testListIOSHostSoftware(t *testing.T, ds *Datastore) {
AppStoreApp: &fleet.SoftwarePackageOrApp{AppStoreID: vpp4},
}
expectedAvailableOnly := map[string]fleet.HostSoftwareWithInstaller{}
expectedAvailableOnly["vpp1ios_apps"] = expected["vpp1ios_apps"]
expectedAvailableOnly["vpp2ios_apps"] = expected["vpp2ios_apps"]
expectedAvailableOnly["vpp3ios_apps"] = expected["vpp3ios_apps"]
expectedAvailableOnly["vpp4ios_apps"] = expected["vpp4ios_apps"]
opts.IncludeAvailableForInstall = true
@@ -4189,12 +4198,12 @@ func testListIOSHostSoftware(t *testing.T, ds *Datastore) {
compareResults(expected, sw, true)
// Available for install only
opts.AvailableForInstall = true
opts.OnlyAvailableForInstall = 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
opts.OnlyAvailableForInstall = false
}
+4 -3
View File
@@ -240,9 +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"`
// OnlyAvailableForInstall is set via a query argument that limits the
// returned software titles to only those that are available for install on
// the host.
OnlyAvailableForInstall bool `query:"available_for_install,optional"`
VulnerableOnly bool `query:"vulnerable,optional"`
}