fix: adjust host filters for VPP software (#20663)

this allows to filter hosts with pending, failed and installed VPP apps.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [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:
Roberto Dip 2024-07-23 14:56:24 -05:00 committed by GitHub
parent 845bfc7e38
commit e4d8fcc3a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 83 additions and 8 deletions

View file

@ -1038,17 +1038,31 @@ func (ds *Datastore) applyHostFilters(
// software (version) ID filter is mutually exclusive with software title ID
// so we're reusing the same filter to avoid adding unnecessary conditions.
if opt.SoftwareStatusFilter != nil {
// get the installer id
meta, err := ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, opt.TeamFilter, *opt.SoftwareTitleIDFilter, false)
if err != nil {
switch {
case fleet.IsNotFound(err):
vppApp, err := ds.GetVPPAppByTeamAndTitleID(ctx, opt.TeamFilter, *opt.SoftwareTitleIDFilter, false)
if err != nil {
return "", nil, ctxerr.Wrap(ctx, err, "get vpp app by team and title id")
}
vppAppJoin, vppAppParams, err := ds.vppAppJoin(vppApp.AdamID, *opt.SoftwareStatusFilter)
if err != nil {
return "", nil, ctxerr.Wrap(ctx, err, "vpp app join")
}
softwareStatusJoin = vppAppJoin
joinParams = append(joinParams, vppAppParams...)
case err != nil:
return "", nil, ctxerr.Wrap(ctx, err, "get software installer metadata by team and title id")
default:
installerJoin, installerParams, err := ds.softwareInstallerJoin(meta.InstallerID, *opt.SoftwareStatusFilter)
if err != nil {
return "", nil, ctxerr.Wrap(ctx, err, "software installer join")
}
softwareStatusJoin = installerJoin
joinParams = append(joinParams, installerParams...)
}
installerJoin, installerParams, err := ds.softwareInstallerJoin(meta.InstallerID, *opt.SoftwareStatusFilter)
if err != nil {
return "", nil, ctxerr.Wrap(ctx, err, "software installer join")
}
softwareStatusJoin = installerJoin
joinParams = append(joinParams, installerParams...)
} else {
softwareFilter = "EXISTS (SELECT 1 FROM host_software hs INNER JOIN software sw ON hs.software_id = sw.id WHERE hs.host_id = h.id AND sw.title_id = ?)"
whereParams = append(whereParams, *opt.SoftwareTitleIDFilter)

View file

@ -416,6 +416,39 @@ WHERE
return &dest, nil
}
func (ds *Datastore) vppAppJoin(adamID string, status fleet.SoftwareInstallerStatus) (string, []interface{}, error) {
stmt := fmt.Sprintf(`JOIN (
SELECT
host_id
FROM
host_vpp_software_installs hvsi
LEFT OUTER JOIN
nano_command_results ncr ON ncr.command_uuid = hvsi.command_uuid
WHERE
adam_id = :adam_id
AND hvsi.id IN(
SELECT
max(id) -- ensure we use only the most recent install attempt for each host
FROM host_vpp_software_installs
WHERE
adam_id = :adam_id
GROUP BY
host_id, adam_id)
AND (%s) = :status) hss ON hss.host_id = h.id
`, vppAppHostStatusNamedQuery("hvsi", "ncr", ""))
return sqlx.Named(stmt, map[string]interface{}{
"status": status,
"adam_id": adamID,
"software_status_installed": fleet.SoftwareInstallerInstalled,
"software_status_failed": fleet.SoftwareInstallerFailed,
"software_status_pending": fleet.SoftwareInstallerPending,
"mdm_status_acknowledged": fleet.MDMAppleStatusAcknowledged,
"mdm_status_error": fleet.MDMAppleStatusError,
"mdm_status_format_error": fleet.MDMAppleStatusCommandFormatError,
})
}
func (ds *Datastore) softwareInstallerJoin(installerID uint, status fleet.SoftwareInstallerStatus) (string, []interface{}, error) {
stmt := fmt.Sprintf(`JOIN (
SELECT

View file

@ -9820,6 +9820,15 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
installResp = installSoftwareResponse{}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", mdmHost.ID, errTitleID), &installSoftwareRequest{}, http.StatusAccepted, &installResp)
// Check if the host is listed as pending
var listResp listHostsResponse
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &listResp, "software_status", "pending", "team_id", strconv.Itoa(int(team.ID)), "software_title_id", strconv.Itoa(int(errTitleID)))
require.Len(t, listResp.Hosts, 1)
require.Equal(t, listResp.Hosts[0].ID, mdmHost.ID)
var countResp countHostsResponse
s.DoJSON("GET", "/api/latest/fleet/hosts/count", nil, http.StatusOK, &countResp, "software_status", "pending", "team_id", strconv.Itoa(int(team.ID)), "software_title_id", strconv.Itoa(int(errTitleID)))
require.Equal(t, 1, countResp.Count)
// Simulate failed installation on the host
cmd, err := mdmDevice.Idle()
var cmdUUID string
@ -9835,6 +9844,14 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
}
}
listResp = listHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &listResp, "software_status", "failed", "team_id", strconv.Itoa(int(team.ID)), "software_title_id", strconv.Itoa(int(errTitleID)))
require.Len(t, listResp.Hosts, 1)
require.Equal(t, listResp.Hosts[0].ID, mdmHost.ID)
countResp = countHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts/count", nil, http.StatusOK, &countResp, "software_status", "failed", "team_id", strconv.Itoa(int(team.ID)), "software_title_id", strconv.Itoa(int(errTitleID)))
require.Equal(t, 1, countResp.Count)
s.lastActivityMatches(
fleet.ActivityInstalledAppStoreApp{}.ActivityName(),
fmt.Sprintf(
@ -9852,6 +9869,10 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
// Trigger install to the host
installResp = installSoftwareResponse{}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", mdmHost.ID, titleID), &installSoftwareRequest{}, http.StatusAccepted, &installResp)
require.Equal(t, listResp.Hosts[0].ID, mdmHost.ID)
countResp = countHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts/count", nil, http.StatusOK, &countResp, "software_status", "pending", "team_id", strconv.Itoa(int(team.ID)), "software_title_id", strconv.Itoa(int(titleID)))
require.Equal(t, 1, countResp.Count)
// Simulate successful installation on the host
cmd, err = mdmDevice.Idle()
@ -9867,6 +9888,13 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
}
}
listResp = listHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &listResp, "software_status", "installed", "team_id", strconv.Itoa(int(team.ID)), "software_title_id", strconv.Itoa(int(titleID)))
require.Len(t, listResp.Hosts, 1)
countResp = countHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts/count", nil, http.StatusOK, &countResp, "software_status", "installed", "team_id", strconv.Itoa(int(team.ID)), "software_title_id", strconv.Itoa(int(titleID)))
require.Equal(t, 1, countResp.Count)
s.lastActivityMatches(
fleet.ActivityInstalledAppStoreApp{}.ActivityName(),
fmt.Sprintf(