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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user