Skip to content

Commit

Permalink
fix: adjust host filters for VPP software (#20663)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
roperzh authored Jul 23, 2024
1 parent 845bfc7 commit e4d8fcc
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 8 deletions.
30 changes: 22 additions & 8 deletions server/datastore/mysql/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 33 additions & 0 deletions server/datastore/mysql/software_installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions server/service/integration_mdm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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()
Expand All @@ -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(
Expand Down

0 comments on commit e4d8fcc

Please sign in to comment.