Remove aggregate host counts from GET /hosts responses (#7510)

This commit is contained in:
Martin Angers 2022-09-06 10:34:06 -04:00 committed by GitHub
parent d98a53f945
commit aa0102d6b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 230 additions and 140 deletions

View File

@ -0,0 +1 @@
* Removed the `hosts_count` field from the `mobile_device_management_solution` and `munki_issue` top-level objects in the `GET /hosts` API endpoint response, as it was a pre-computed count that is updated at intervals, not a live count, and it could be confusing when it did not match the number of hosts returned.

View File

@ -1743,9 +1743,9 @@ If `additional_info_filters` is not specified, no `additional` information will
If `software_id` is specified, an additional top-level key `"software"` is returned with the software object corresponding to the `software_id`. See [List all software](#list-all-software) response payload for details about this object.
If `mdm_id` is specified, an additional top-level key `"mobile_device_management_solution"` is returned with the aggregated statistics corresponding to the `mdm_id` (and, if provided, `team_id`). See [Get aggregated host's mobile device management (MDM) and Munki information](#get-aggregated-hosts-mobile-device-management-mdm-and-munki-information) response payload for details about this object. Note that the statistics are for the corresponding `mdm_id` (and `team_id` if provided) only, and do not take into account other potential filters such as `mdm_enrollment_status`.
If `mdm_id` is specified, an additional top-level key `"mobile_device_management_solution"` is returned with the information corresponding to the `mdm_id`.
If `munki_issue_id` is specified, an additional top-level key `"munki_issue"` is returned with the aggregated statistics corresponding to the `munki_issue_id` (and, if provided, `team_id`). See [Get aggregated host's mobile device management (MDM) and Munki information](#get-aggregated-hosts-mobile-device-management-mdm-and-munki-information) response payload for details about this object.
If `munki_issue_id` is specified, an additional top-level key `"munki_issue"` is returned with the information corresponding to the `munki_issue_id`.
#### Example
@ -1826,6 +1826,32 @@ If `munki_issue_id` is specified, an additional top-level key `"munki_issue"` is
> Note: the response above assumes a [GeoIP database is configured](https://fleetdm.com/docs/deploying/configuration#geo-ip), otherwise the `geolocation` object won't be included.
Response payload with the `mdm_id` filter provided:
```json
{
"hosts": [...],
"mobile_device_management_solution": {
"server_url": "http://some.url/mdm",
"name": "MDM Vendor Name",
"id": 999
}
}
```
Response payload with the `munki_issue_id` filter provided:
```json
{
"hosts": [...],
"munki_issue": {
"id": 1,
"name": "Could not retrieve managed install primary manifest",
"type": "error"
}
}
```
### Count hosts
`GET /api/v1/fleet/hosts/count`

View File

@ -1670,7 +1670,7 @@ func (ds *Datastore) getOrInsertMDMSolution(ctx context.Context, serverURL strin
return ds.optimisticGetOrInsert(ctx, readStmt, insStmt)
}
func (ds *Datastore) GetMunkiVersion(ctx context.Context, hostID uint) (string, error) {
func (ds *Datastore) GetHostMunkiVersion(ctx context.Context, hostID uint) (string, error) {
var version string
err := sqlx.GetContext(ctx, ds.reader, &version, `SELECT version FROM host_munki_info WHERE deleted_at is NULL AND host_id = ?`, hostID)
if err != nil {
@ -1683,7 +1683,7 @@ func (ds *Datastore) GetMunkiVersion(ctx context.Context, hostID uint) (string,
return version, nil
}
func (ds *Datastore) GetMDM(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
func (ds *Datastore) GetHostMDM(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
var hmdm fleet.HostMDM
err := sqlx.GetContext(ctx, ds.reader, &hmdm, `
SELECT
@ -1703,7 +1703,26 @@ func (ds *Datastore) GetMDM(ctx context.Context, hostID uint) (*fleet.HostMDM, e
return &hmdm, nil
}
func (ds *Datastore) GetMunkiIssues(ctx context.Context, hostID uint) ([]*fleet.HostMunkiIssue, error) {
func (ds *Datastore) GetMDMSolution(ctx context.Context, mdmID uint) (*fleet.MDMSolution, error) {
var solution fleet.MDMSolution
err := sqlx.GetContext(ctx, ds.reader, &solution, `
SELECT
id,
name,
server_url
FROM
mobile_device_management_solutions
WHERE id = ?`, mdmID)
if err != nil {
if err == sql.ErrNoRows {
return nil, ctxerr.Wrap(ctx, notFound("MDMSolution").WithID(mdmID))
}
return nil, ctxerr.Wrapf(ctx, err, "select mobile_device_management_solutions for id %d", mdmID)
}
return &solution, nil
}
func (ds *Datastore) GetHostMunkiIssues(ctx context.Context, hostID uint) ([]*fleet.HostMunkiIssue, error) {
var issues []*fleet.HostMunkiIssue
err := sqlx.SelectContext(ctx, ds.reader, &issues, `
SELECT
@ -1725,6 +1744,25 @@ func (ds *Datastore) GetMunkiIssues(ctx context.Context, hostID uint) ([]*fleet.
return issues, nil
}
func (ds *Datastore) GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*fleet.MunkiIssue, error) {
var issue fleet.MunkiIssue
err := sqlx.GetContext(ctx, ds.reader, &issue, `
SELECT
id,
name,
issue_type
FROM
munki_issues
WHERE id = ?`, munkiIssueID)
if err != nil {
if err == sql.ErrNoRows {
return nil, ctxerr.Wrap(ctx, notFound("MunkiIssue").WithID(munkiIssueID))
}
return nil, ctxerr.Wrapf(ctx, err, "select munki_issues for id %d", munkiIssueID)
}
return &issue, nil
}
func (ds *Datastore) AggregatedMunkiVersion(ctx context.Context, teamID *uint) ([]fleet.AggregatedMunkiVersion, time.Time, error) {
id := uint(0)

View File

@ -948,7 +948,7 @@ func testHostsListMunkiIssueID(t *testing.T, ds *Datastore) {
err = ds.SetOrUpdateMunkiInfo(ctx, hostIDs[0], "1.0.0", []string{strings.Repeat("Z", maxMunkiIssueNameLen)}, []string{strings.Repeat("💞", maxMunkiIssueNameLen)})
require.NoError(t, err)
issues, err := ds.GetMunkiIssues(ctx, hostIDs[0])
issues, err := ds.GetHostMunkiIssues(ctx, hostIDs[0])
require.NoError(t, err)
require.Len(t, issues, 2)
names := []string{issues[0].Name, issues[1].Name}
@ -961,7 +961,7 @@ func testHostsListMunkiIssueID(t *testing.T, ds *Datastore) {
err = ds.SetOrUpdateMunkiInfo(ctx, hostIDs[0], "1.0.0", []string{strings.Repeat("A", maxMunkiIssueNameLen+1)}, []string{strings.Repeat("☺", maxMunkiIssueNameLen+1)})
require.NoError(t, err)
issues, err = ds.GetMunkiIssues(ctx, hostIDs[0])
issues, err = ds.GetHostMunkiIssues(ctx, hostIDs[0])
require.NoError(t, err)
require.Len(t, issues, 2)
names = []string{issues[0].Name, issues[1].Name}
@ -3795,26 +3795,40 @@ func assertHostDeviceMapping(t *testing.T, got, want []*fleet.HostDeviceMapping)
}
func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
_, err := ds.GetMunkiVersion(context.Background(), 123)
_, err := ds.GetHostMunkiVersion(context.Background(), 123)
require.True(t, fleet.IsNotFound(err))
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), 123, "1.2.3", nil, nil))
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), 999, "9.0", nil, nil))
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), 123, "1.3.0", []string{"a", "b"}, []string{"c"}))
version, err := ds.GetMunkiVersion(context.Background(), 123)
version, err := ds.GetHostMunkiVersion(context.Background(), 123)
require.NoError(t, err)
require.Equal(t, "1.3.0", version)
issues, err := ds.GetMunkiIssues(context.Background(), 123)
issues, err := ds.GetHostMunkiIssues(context.Background(), 123)
require.NoError(t, err)
require.Len(t, issues, 3)
var aMunkiIssueID uint
for _, iss := range issues {
assert.NotZero(t, iss.MunkiIssueID)
if iss.Name == "a" {
aMunkiIssueID = iss.MunkiIssueID
}
assert.False(t, iss.HostIssueCreatedAt.IsZero())
}
// get a Munki Issue
miss, err := ds.GetMunkiIssue(context.Background(), aMunkiIssueID)
require.NoError(t, err)
require.Equal(t, "a", miss.Name)
// get an invalid munki issue
_, err = ds.GetMunkiIssue(context.Background(), aMunkiIssueID+1000)
require.Error(t, err)
require.ErrorIs(t, err, sql.ErrNoRows)
// ignore IDs and timestamps in slice comparison
issues[0].MunkiIssueID, issues[0].HostIssueCreatedAt = 0, time.Time{}
issues[1].MunkiIssueID, issues[1].HostIssueCreatedAt = 0, time.Time{}
@ -3825,29 +3839,29 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
{Name: "c", IssueType: "warning"},
}, issues)
version, err = ds.GetMunkiVersion(context.Background(), 999)
version, err = ds.GetHostMunkiVersion(context.Background(), 999)
require.NoError(t, err)
require.Equal(t, "9.0", version)
issues, err = ds.GetMunkiIssues(context.Background(), 999)
issues, err = ds.GetHostMunkiIssues(context.Background(), 999)
require.NoError(t, err)
require.Len(t, issues, 0)
// simulate uninstall
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), 123, "", nil, nil))
_, err = ds.GetMunkiVersion(context.Background(), 123)
_, err = ds.GetHostMunkiVersion(context.Background(), 123)
require.True(t, fleet.IsNotFound(err))
issues, err = ds.GetMunkiIssues(context.Background(), 123)
issues, err = ds.GetHostMunkiIssues(context.Background(), 123)
require.NoError(t, err)
require.Len(t, issues, 0)
_, err = ds.GetMDM(context.Background(), 432)
_, err = ds.GetHostMDM(context.Background(), 432)
require.True(t, fleet.IsNotFound(err), err)
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, true, "url", false))
hmdm, err := ds.GetMDM(context.Background(), 432)
hmdm, err := ds.GetHostMDM(context.Background(), 432)
require.NoError(t, err)
assert.True(t, hmdm.Enrolled)
assert.Equal(t, "url", hmdm.ServerURL)
@ -3860,7 +3874,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://kandji.io", true)) // kandji mdm name
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, false, "url3", true))
hmdm, err = ds.GetMDM(context.Background(), 432)
hmdm, err = ds.GetHostMDM(context.Background(), 432)
require.NoError(t, err)
assert.False(t, hmdm.Enrolled)
assert.Equal(t, "url3", hmdm.ServerURL)
@ -3870,7 +3884,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
assert.NotEqual(t, urlMDMID, *hmdm.MDMID)
assert.Equal(t, fleet.UnknownMDMName, hmdm.Name)
hmdm, err = ds.GetMDM(context.Background(), 455)
hmdm, err = ds.GetHostMDM(context.Background(), 455)
require.NoError(t, err)
assert.True(t, hmdm.Enrolled)
assert.Equal(t, "https://kandji.io", hmdm.ServerURL)
@ -3880,10 +3894,21 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
kandjiID1 := *hmdm.MDMID
assert.Equal(t, fleet.WellKnownMDMKandji, hmdm.Name)
// get mdm solution
mdmSol, err := ds.GetMDMSolution(context.Background(), kandjiID1)
require.NoError(t, err)
require.Equal(t, "https://kandji.io", mdmSol.ServerURL)
require.Equal(t, fleet.WellKnownMDMKandji, mdmSol.Name)
// get unknown mdm solution
_, err = ds.GetMDMSolution(context.Background(), kandjiID1+1000)
require.Error(t, err)
require.ErrorIs(t, err, sql.ErrNoRows)
// switch to simplemdm in an update
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://simplemdm.com", false)) // now simplemdm name
hmdm, err = ds.GetMDM(context.Background(), 455)
hmdm, err = ds.GetHostMDM(context.Background(), 455)
require.NoError(t, err)
assert.True(t, hmdm.Enrolled)
assert.Equal(t, "https://simplemdm.com", hmdm.ServerURL)
@ -3895,7 +3920,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
// switch back to "url"
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, false, "url", false))
hmdm, err = ds.GetMDM(context.Background(), 455)
hmdm, err = ds.GetHostMDM(context.Background(), 455)
require.NoError(t, err)
assert.False(t, hmdm.Enrolled)
assert.Equal(t, "url", hmdm.ServerURL)
@ -3908,7 +3933,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
// even though this is another Kandji, the URL is different.
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://kandji.io/2", false))
hmdm, err = ds.GetMDM(context.Background(), 455)
hmdm, err = ds.GetHostMDM(context.Background(), 455)
require.NoError(t, err)
assert.True(t, hmdm.Enrolled)
assert.Equal(t, "https://kandji.io/2", hmdm.ServerURL)
@ -3961,7 +3986,7 @@ func testMunkiIssuesBatchSize(t *testing.T, ds *Datastore) {
// try those errors/warning with some hosts
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), 123, "1.2.3", []string{"a", "b"}, []string{"C"}))
issues, err := ds.GetMunkiIssues(ctx, 123)
issues, err := ds.GetHostMunkiIssues(ctx, 123)
require.NoError(t, err)
require.Len(t, issues, 3)
for _, iss := range issues {
@ -3969,7 +3994,7 @@ func testMunkiIssuesBatchSize(t *testing.T, ds *Datastore) {
}
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), 123, "1.2.3", []string{"c", "z"}, []string{"D", "E", "Z"}))
issues, err = ds.GetMunkiIssues(ctx, 123)
issues, err = ds.GetHostMunkiIssues(ctx, 123)
require.NoError(t, err)
require.Len(t, issues, 5)
for _, iss := range issues {
@ -4054,18 +4079,24 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
issues[2].ID = 0
assert.ElementsMatch(t, issues, []fleet.AggregatedMunkiIssue{
{
Name: "a",
IssueType: "error",
MunkiIssue: fleet.MunkiIssue{
Name: "a",
IssueType: "error",
},
HostsCount: 2,
},
{
Name: "b",
IssueType: "error",
MunkiIssue: fleet.MunkiIssue{
Name: "b",
IssueType: "error",
},
HostsCount: 1,
},
{
Name: "c",
IssueType: "warning",
MunkiIssue: fleet.MunkiIssue{
Name: "c",
IssueType: "warning",
},
HostsCount: 2,
},
})
@ -4157,13 +4188,17 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
issues[1].ID = 0
assert.ElementsMatch(t, issues, []fleet.AggregatedMunkiIssue{
{
Name: "d",
IssueType: "error",
MunkiIssue: fleet.MunkiIssue{
Name: "d",
IssueType: "error",
},
HostsCount: 2,
},
{
Name: "f",
IssueType: "warning",
MunkiIssue: fleet.MunkiIssue{
Name: "f",
IssueType: "warning",
},
HostsCount: 1,
},
})

View File

@ -240,9 +240,9 @@ type Datastore interface {
// ListPoliciesForHost lists the policies that a host will check and whether they are passing
ListPoliciesForHost(ctx context.Context, host *Host) ([]*HostPolicy, error)
GetMunkiVersion(ctx context.Context, hostID uint) (string, error)
GetMunkiIssues(ctx context.Context, hostID uint) ([]*HostMunkiIssue, error)
GetMDM(ctx context.Context, hostID uint) (*HostMDM, error)
GetHostMunkiVersion(ctx context.Context, hostID uint) (string, error)
GetHostMunkiIssues(ctx context.Context, hostID uint) ([]*HostMunkiIssue, error)
GetHostMDM(ctx context.Context, hostID uint) (*HostMDM, error)
AggregatedMunkiVersion(ctx context.Context, teamID *uint) ([]AggregatedMunkiVersion, time.Time, error)
AggregatedMunkiIssues(ctx context.Context, teamID *uint) ([]AggregatedMunkiIssue, time.Time, error)
@ -250,6 +250,9 @@ type Datastore interface {
AggregatedMDMSolutions(ctx context.Context, teamID *uint) ([]AggregatedMDMSolutions, time.Time, error)
GenerateAggregatedMunkiAndMDM(ctx context.Context) error
GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*MunkiIssue, error)
GetMDMSolution(ctx context.Context, mdmID uint) (*MDMSolution, error)
OSVersions(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*OSVersions, error)
UpdateOSVersions(ctx context.Context) error

View File

@ -422,11 +422,17 @@ type AggregatedMunkiVersion struct {
HostsCount int `json:"hosts_count" db:"hosts_count"`
}
// MunkiIssue represents a single munki issue, as returned by the list hosts
// endpoint when a muniki issue ID is provided as filter.
type MunkiIssue struct {
ID uint `json:"id" db:"id"`
Name string `json:"name" db:"name"`
IssueType string `json:"type" db:"issue_type"`
}
type AggregatedMunkiIssue struct {
ID uint `json:"id" db:"id"`
Name string `json:"name" db:"name"`
IssueType string `json:"type" db:"issue_type"`
HostsCount int `json:"hosts_count" db:"hosts_count"`
MunkiIssue
HostsCount int `json:"hosts_count" db:"hosts_count"`
}
type AggregatedMDMStatus struct {
@ -436,11 +442,17 @@ type AggregatedMDMStatus struct {
HostsCount int `json:"hosts_count" db:"hosts_count"`
}
// MDMSolution represents a single MDM solution, as returned by the list hosts
// endpoint when an MDM Solution ID is provided as filter.
type MDMSolution struct {
ID uint `json:"id" db:"id"`
Name string `json:"name" db:"name"`
ServerURL string `json:"server_url" db:"server_url"`
}
type AggregatedMDMSolutions struct {
ID uint `json:"id,omitempty" db:"id"`
Name string `json:"name,omitempty" db:"name"`
HostsCount int `json:"hosts_count" db:"hosts_count"`
ServerURL string `json:"server_url" db:"server_url"`
MDMSolution
HostsCount int `json:"hosts_count" db:"hosts_count"`
}
type AggregatedMacadminsData struct {

View File

@ -298,8 +298,8 @@ type Service interface {
MacadminsData(ctx context.Context, id uint) (*MacadminsData, error)
AggregatedMacadminsData(ctx context.Context, teamID *uint) (*AggregatedMacadminsData, error)
AggregatedMDMSolutions(ctx context.Context, teamID *uint, mdmID uint) (*AggregatedMDMSolutions, error)
AggregatedMunkiIssue(ctx context.Context, teamID *uint, munkiIssueID uint) (*AggregatedMunkiIssue, error)
GetMDMSolution(ctx context.Context, mdmID uint) (*MDMSolution, error)
GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*MunkiIssue, error)
// OSVersions returns a list of operating systems and associated host counts, which may be
// filtered using the following optional criteria: team id, platform, or name and version.

View File

@ -189,11 +189,11 @@ type SetOrUpdateDeviceAuthTokenFunc func(ctx context.Context, hostID uint, authT
type ListPoliciesForHostFunc func(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error)
type GetMunkiVersionFunc func(ctx context.Context, hostID uint) (string, error)
type GetHostMunkiVersionFunc func(ctx context.Context, hostID uint) (string, error)
type GetMunkiIssuesFunc func(ctx context.Context, hostID uint) ([]*fleet.HostMunkiIssue, error)
type GetHostMunkiIssuesFunc func(ctx context.Context, hostID uint) ([]*fleet.HostMunkiIssue, error)
type GetMDMFunc func(ctx context.Context, hostID uint) (*fleet.HostMDM, error)
type GetHostMDMFunc func(ctx context.Context, hostID uint) (*fleet.HostMDM, error)
type AggregatedMunkiVersionFunc func(ctx context.Context, teamID *uint) ([]fleet.AggregatedMunkiVersion, time.Time, error)
@ -205,6 +205,10 @@ type AggregatedMDMSolutionsFunc func(ctx context.Context, teamID *uint) ([]fleet
type GenerateAggregatedMunkiAndMDMFunc func(ctx context.Context) error
type GetMunkiIssueFunc func(ctx context.Context, munkiIssueID uint) (*fleet.MunkiIssue, error)
type GetMDMSolutionFunc func(ctx context.Context, mdmID uint) (*fleet.MDMSolution, error)
type OSVersionsFunc func(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error)
type UpdateOSVersionsFunc func(ctx context.Context) error
@ -708,14 +712,14 @@ type DataStore struct {
ListPoliciesForHostFunc ListPoliciesForHostFunc
ListPoliciesForHostFuncInvoked bool
GetMunkiVersionFunc GetMunkiVersionFunc
GetMunkiVersionFuncInvoked bool
GetHostMunkiVersionFunc GetHostMunkiVersionFunc
GetHostMunkiVersionFuncInvoked bool
GetMunkiIssuesFunc GetMunkiIssuesFunc
GetMunkiIssuesFuncInvoked bool
GetHostMunkiIssuesFunc GetHostMunkiIssuesFunc
GetHostMunkiIssuesFuncInvoked bool
GetMDMFunc GetMDMFunc
GetMDMFuncInvoked bool
GetHostMDMFunc GetHostMDMFunc
GetHostMDMFuncInvoked bool
AggregatedMunkiVersionFunc AggregatedMunkiVersionFunc
AggregatedMunkiVersionFuncInvoked bool
@ -732,6 +736,12 @@ type DataStore struct {
GenerateAggregatedMunkiAndMDMFunc GenerateAggregatedMunkiAndMDMFunc
GenerateAggregatedMunkiAndMDMFuncInvoked bool
GetMunkiIssueFunc GetMunkiIssueFunc
GetMunkiIssueFuncInvoked bool
GetMDMSolutionFunc GetMDMSolutionFunc
GetMDMSolutionFuncInvoked bool
OSVersionsFunc OSVersionsFunc
OSVersionsFuncInvoked bool
@ -1530,19 +1540,19 @@ func (s *DataStore) ListPoliciesForHost(ctx context.Context, host *fleet.Host) (
return s.ListPoliciesForHostFunc(ctx, host)
}
func (s *DataStore) GetMunkiVersion(ctx context.Context, hostID uint) (string, error) {
s.GetMunkiVersionFuncInvoked = true
return s.GetMunkiVersionFunc(ctx, hostID)
func (s *DataStore) GetHostMunkiVersion(ctx context.Context, hostID uint) (string, error) {
s.GetHostMunkiVersionFuncInvoked = true
return s.GetHostMunkiVersionFunc(ctx, hostID)
}
func (s *DataStore) GetMunkiIssues(ctx context.Context, hostID uint) ([]*fleet.HostMunkiIssue, error) {
s.GetMunkiIssuesFuncInvoked = true
return s.GetMunkiIssuesFunc(ctx, hostID)
func (s *DataStore) GetHostMunkiIssues(ctx context.Context, hostID uint) ([]*fleet.HostMunkiIssue, error) {
s.GetHostMunkiIssuesFuncInvoked = true
return s.GetHostMunkiIssuesFunc(ctx, hostID)
}
func (s *DataStore) GetMDM(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
s.GetMDMFuncInvoked = true
return s.GetMDMFunc(ctx, hostID)
func (s *DataStore) GetHostMDM(ctx context.Context, hostID uint) (*fleet.HostMDM, error) {
s.GetHostMDMFuncInvoked = true
return s.GetHostMDMFunc(ctx, hostID)
}
func (s *DataStore) AggregatedMunkiVersion(ctx context.Context, teamID *uint) ([]fleet.AggregatedMunkiVersion, time.Time, error) {
@ -1570,6 +1580,16 @@ func (s *DataStore) GenerateAggregatedMunkiAndMDM(ctx context.Context) error {
return s.GenerateAggregatedMunkiAndMDMFunc(ctx)
}
func (s *DataStore) GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*fleet.MunkiIssue, error) {
s.GetMunkiIssueFuncInvoked = true
return s.GetMunkiIssueFunc(ctx, munkiIssueID)
}
func (s *DataStore) GetMDMSolution(ctx context.Context, mdmID uint) (*fleet.MDMSolution, error) {
s.GetMDMSolutionFuncInvoked = true
return s.GetMDMSolutionFunc(ctx, mdmID)
}
func (s *DataStore) OSVersions(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
s.OSVersionsFuncInvoked = true
return s.OSVersionsFunc(ctx, teamID, platform, name, version)

View File

@ -72,12 +72,12 @@ type listHostsResponse struct {
// MDMSolution is populated with the MDM solution corresponding to the mdm_id
// filter if one is provided with the request (and it exists in the
// database). It is nil otherwise and absent of the JSON response payload.
MDMSolution *fleet.AggregatedMDMSolutions `json:"mobile_device_management_solution,omitempty"`
MDMSolution *fleet.MDMSolution `json:"mobile_device_management_solution,omitempty"`
// MunkiIssue is populated with the munki issue corresponding to the
// munki_issue_id filter if one is provided with the request (and it exists
// in the database). It is nil otherwise and absent of the JSON response
// payload.
MunkiIssue *fleet.AggregatedMunkiIssue `json:"munki_issue,omitempty"`
MunkiIssue *fleet.MunkiIssue `json:"munki_issue,omitempty"`
Err error `json:"error,omitempty"`
}
@ -96,20 +96,20 @@ func listHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Servi
}
}
var mdmSolution *fleet.AggregatedMDMSolutions
var mdmSolution *fleet.MDMSolution
if req.Opts.MDMIDFilter != nil {
var err error
mdmSolution, err = svc.AggregatedMDMSolutions(ctx, req.Opts.TeamFilter, *req.Opts.MDMIDFilter)
if err != nil {
mdmSolution, err = svc.GetMDMSolution(ctx, *req.Opts.MDMIDFilter)
if err != nil && !fleet.IsNotFound(err) { // ignore not found, just return nil for the MDM solution in that case
return listHostsResponse{Err: err}, nil
}
}
var munkiIssue *fleet.AggregatedMunkiIssue
var munkiIssue *fleet.MunkiIssue
if req.Opts.MunkiIssueIDFilter != nil {
var err error
munkiIssue, err = svc.AggregatedMunkiIssue(ctx, req.Opts.TeamFilter, *req.Opts.MunkiIssueIDFilter)
if err != nil {
munkiIssue, err = svc.GetMunkiIssue(ctx, *req.Opts.MunkiIssueIDFilter)
if err != nil && !fleet.IsNotFound(err) { // ignore not found, just return nil for the munki issue in that case
return listHostsResponse{Err: err}, nil
}
}
@ -136,67 +136,20 @@ func listHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Servi
}, nil
}
func (svc *Service) AggregatedMDMSolutions(ctx context.Context, teamID *uint, mdmID uint) (*fleet.AggregatedMDMSolutions, error) {
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionList); err != nil {
func (svc *Service) GetMDMSolution(ctx context.Context, mdmID uint) (*fleet.MDMSolution, error) {
// require list hosts permission to view this information
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return nil, err
}
if teamID != nil {
_, err := svc.ds.Team(ctx, *teamID)
if err != nil {
return nil, err
}
}
// it is expected that there will be relatively few MDM solutions. This
// returns the slice of all aggregated stats (one entry per mdm_id), and we
// then iterate to return only the one that was requested (the slice is
// stored as-is in a JSON field in the database).
sols, _, err := svc.ds.AggregatedMDMSolutions(ctx, teamID)
if err != nil {
return nil, err
}
for _, sol := range sols {
// don't take the address of the loop variable (although it could be ok
// here, but just bad practice)
sol := sol
if sol.ID == mdmID {
return &sol, nil
}
}
return nil, nil
return svc.ds.GetMDMSolution(ctx, mdmID)
}
func (svc *Service) AggregatedMunkiIssue(ctx context.Context, teamID *uint, munkiIssueID uint) (*fleet.AggregatedMunkiIssue, error) {
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionList); err != nil {
func (svc *Service) GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*fleet.MunkiIssue, error) {
// require list hosts permission to view this information
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return nil, err
}
if teamID != nil {
_, err := svc.ds.Team(ctx, *teamID)
if err != nil {
return nil, err
}
}
// This returns the slice of all aggregated stats (one entry per
// munki_issue_id), and we then iterate to return only the one that was
// requested (the slice is stored as-is in a JSON field in the database).
issues, _, err := svc.ds.AggregatedMunkiIssues(ctx, teamID)
if err != nil {
return nil, err
}
for _, iss := range issues {
// don't take the address of the loop variable (although it could be ok
// here, but just bad practice)
iss := iss
if iss.ID == munkiIssueID {
return &iss, nil
}
}
return nil, nil
return svc.ds.GetMunkiIssue(ctx, munkiIssueID)
}
func (svc *Service) ListHosts(ctx context.Context, opt fleet.HostListOptions) ([]*fleet.Host, error) {
@ -982,7 +935,7 @@ func (svc *Service) MacadminsData(ctx context.Context, id uint) (*fleet.Macadmin
}
var munkiInfo *fleet.HostMunkiInfo
switch version, err := svc.ds.GetMunkiVersion(ctx, id); {
switch version, err := svc.ds.GetHostMunkiVersion(ctx, id); {
case err != nil && !fleet.IsNotFound(err):
return nil, err
case err == nil:
@ -990,7 +943,7 @@ func (svc *Service) MacadminsData(ctx context.Context, id uint) (*fleet.Macadmin
}
var mdm *fleet.HostMDM
switch hmdm, err := svc.ds.GetMDM(ctx, id); {
switch hmdm, err := svc.ds.GetHostMDM(ctx, id); {
case err != nil && !fleet.IsNotFound(err):
return nil, err
case err == nil:
@ -998,7 +951,7 @@ func (svc *Service) MacadminsData(ctx context.Context, id uint) (*fleet.Macadmin
}
var munkiIssues []*fleet.HostMunkiIssue
switch issues, err := svc.ds.GetMunkiIssues(ctx, id); {
switch issues, err := svc.ds.GetHostMunkiIssues(ctx, id); {
case err != nil:
return nil, err
case err == nil:

View File

@ -1120,7 +1120,6 @@ func (s *integrationTestSuite) TestListHosts() {
assert.Nil(t, resp.MunkiIssue)
require.NotNil(t, resp.MDMSolution)
assert.Equal(t, mdmID, resp.MDMSolution.ID)
assert.Equal(t, 1, resp.MDMSolution.HostsCount)
assert.Equal(t, fleet.WellKnownMDMSimpleMDM, resp.MDMSolution.Name)
assert.Equal(t, "https://simplemdm.com", resp.MDMSolution.ServerURL)
@ -1151,11 +1150,10 @@ func (s *integrationTestSuite) TestListHosts() {
assert.Nil(t, resp.Software)
assert.Nil(t, resp.MDMSolution)
require.NotNil(t, resp.MunkiIssue)
assert.Equal(t, fleet.AggregatedMunkiIssue{
ID: errMunkiID,
Name: "err",
IssueType: "error",
HostsCount: 1,
assert.Equal(t, fleet.MunkiIssue{
ID: errMunkiID,
Name: "err",
IssueType: "error",
}, *resp.MunkiIssue)
// filters can be combined, no problem
@ -2683,13 +2681,17 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
agg.Macadmins.MunkiIssues[1].ID = 0
assert.ElementsMatch(t, agg.Macadmins.MunkiIssues, []fleet.AggregatedMunkiIssue{
{
Name: "error1",
IssueType: "error",
MunkiIssue: fleet.MunkiIssue{
Name: "error1",
IssueType: "error",
},
HostsCount: 1,
},
{
Name: "warning1",
IssueType: "warning",
MunkiIssue: fleet.MunkiIssue{
Name: "warning1",
IssueType: "warning",
},
HostsCount: 1,
},
})