mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
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:
parent
845bfc7e38
commit
e4d8fcc3a5
3 changed files with 83 additions and 8 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue