mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
Filter hosts and label counts by teams (#949)
- Add TeamFilter to relevant host and label methods. - Pass appropriate filter in service methods. The dashboard should now show the appropriate hosts for a user's team membership.
This commit is contained in:
parent
b3bafdce24
commit
e4358a92bc
@ -250,28 +250,29 @@ func testListHosts(t *testing.T, ds kolide.Datastore) {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
hosts2, err := ds.ListHosts(kolide.HostListOptions{})
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
hosts2, err := ds.ListHosts(filter, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(hosts), len(hosts2))
|
||||
|
||||
// Test with logic for only a few hosts
|
||||
hosts2, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{PerPage: 4, Page: 0}})
|
||||
hosts2, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{PerPage: 4, Page: 0}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 4, len(hosts2))
|
||||
|
||||
err = ds.DeleteHost(hosts[0].ID)
|
||||
require.Nil(t, err)
|
||||
hosts2, err = ds.ListHosts(kolide.HostListOptions{})
|
||||
hosts2, err = ds.ListHosts(filter, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(hosts)-1, len(hosts2))
|
||||
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{})
|
||||
hosts, err = ds.ListHosts(filter, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, len(hosts2), len(hosts))
|
||||
|
||||
err = ds.SaveHost(hosts[0])
|
||||
require.Nil(t, err)
|
||||
hosts2, err = ds.ListHosts(kolide.HostListOptions{})
|
||||
hosts2, err = ds.ListHosts(filter, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, hosts[0].ID, hosts2[0].ID)
|
||||
}
|
||||
@ -288,25 +289,27 @@ func testListHostsFilterAdditional(t *testing.T, ds kolide.Datastore) {
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
|
||||
// Add additional
|
||||
additional := json.RawMessage(`{"field1": "v1", "field2": "v2"}`)
|
||||
h.Additional = &additional
|
||||
require.NoError(t, ds.SaveHostAdditional(h))
|
||||
|
||||
hosts, err := ds.ListHosts(kolide.HostListOptions{})
|
||||
hosts, err := ds.ListHosts(filter, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
assert.Nil(t, hosts[0].Additional)
|
||||
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "field2"}})
|
||||
hosts, err = ds.ListHosts(filter, kolide.HostListOptions{AdditionalFilters: []string{"field1", "field2"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, &additional, hosts[0].Additional)
|
||||
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"*"}})
|
||||
hosts, err = ds.ListHosts(filter, kolide.HostListOptions{AdditionalFilters: []string{"*"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, &additional, hosts[0].Additional)
|
||||
|
||||
additional = json.RawMessage(`{"field1": "v1", "missing": null}`)
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "missing"}})
|
||||
hosts, err = ds.ListHosts(filter, kolide.HostListOptions{AdditionalFilters: []string{"field1", "missing"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, &additional, hosts[0].Additional)
|
||||
}
|
||||
@ -328,19 +331,21 @@ func testListHostsStatus(t *testing.T, ds kolide.Datastore) {
|
||||
}
|
||||
}
|
||||
|
||||
hosts, err := ds.ListHosts(kolide.HostListOptions{StatusFilter: "online"})
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
|
||||
hosts, err := ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "online"})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(hosts))
|
||||
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{StatusFilter: "offline"})
|
||||
hosts, err = ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "offline"})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 9, len(hosts))
|
||||
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{StatusFilter: "mia"})
|
||||
hosts, err = ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "mia"})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 0, len(hosts))
|
||||
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{StatusFilter: "new"})
|
||||
hosts, err = ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "new"})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(hosts))
|
||||
}
|
||||
@ -364,47 +369,49 @@ func testListHostsQuery(t *testing.T, ds kolide.Datastore) {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
gotHosts, err := ds.ListHosts(kolide.HostListOptions{})
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
|
||||
gotHosts, err := ds.ListHosts(filter, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(hosts), len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "00"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "00"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "000"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "000"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168."}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168."}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168.1.1"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168.1.1"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%00"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%00"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%003"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%003"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_006"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_006"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(gotHosts))
|
||||
|
||||
gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial009"}})
|
||||
gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial009"}})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(gotHosts))
|
||||
}
|
||||
@ -551,8 +558,7 @@ func testSearchHosts(t *testing.T, ds kolide.Datastore) {
|
||||
}
|
||||
|
||||
func testSearchHostsLimit(t *testing.T, ds kolide.Datastore) {
|
||||
user := &kolide.User{GlobalRole: ptr.String(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
_, err := ds.NewHost(&kolide.Host{
|
||||
@ -573,14 +579,10 @@ func testSearchHostsLimit(t *testing.T, ds kolide.Datastore) {
|
||||
}
|
||||
|
||||
func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) {
|
||||
if ds.Name() == "inmem" {
|
||||
fmt.Println("Busted test skipped for inmem")
|
||||
return
|
||||
}
|
||||
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
mockClock := clock.NewMockClock()
|
||||
|
||||
online, offline, mia, new, err := ds.GenerateHostStatusStatistics(mockClock.Now())
|
||||
online, offline, mia, new, err := ds.GenerateHostStatusStatistics(filter, mockClock.Now())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(0), online)
|
||||
assert.Equal(t, uint(0), offline)
|
||||
@ -640,14 +642,14 @@ func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) {
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
online, offline, mia, new, err = ds.GenerateHostStatusStatistics(mockClock.Now())
|
||||
online, offline, mia, new, err = ds.GenerateHostStatusStatistics(filter, mockClock.Now())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(2), online)
|
||||
assert.Equal(t, uint(1), offline)
|
||||
assert.Equal(t, uint(1), mia)
|
||||
assert.Equal(t, uint(4), new)
|
||||
|
||||
online, offline, mia, new, err = ds.GenerateHostStatusStatistics(mockClock.Now().Add(1 * time.Hour))
|
||||
online, offline, mia, new, err = ds.GenerateHostStatusStatistics(filter, mockClock.Now().Add(1*time.Hour))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(0), online)
|
||||
assert.Equal(t, uint(3), offline)
|
||||
@ -811,7 +813,8 @@ func testHostIDsByName(t *testing.T, ds kolide.Datastore) {
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
hosts, err := ds.HostIDsByName([]string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
hosts, err := ds.HostIDsByName(filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
require.Nil(t, err)
|
||||
sort.Slice(hosts, func(i, j int) bool { return hosts[i] < hosts[j] })
|
||||
assert.Equal(t, hosts, []uint{2, 3, 6})
|
||||
|
@ -335,9 +335,10 @@ func testListHostsInLabel(t *testing.T, db kolide.Datastore) {
|
||||
err = db.ApplyLabelSpecs([]*kolide.LabelSpec{l1})
|
||||
require.Nil(t, err)
|
||||
|
||||
{
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
|
||||
hosts, err := db.ListHostsInLabel(l1.ID, kolide.HostListOptions{})
|
||||
{
|
||||
hosts, err := db.ListHostsInLabel(filter, l1.ID, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, hosts, 0)
|
||||
}
|
||||
@ -348,7 +349,7 @@ func testListHostsInLabel(t *testing.T, db kolide.Datastore) {
|
||||
}
|
||||
|
||||
{
|
||||
hosts, err := db.ListHostsInLabel(l1.ID, kolide.HostListOptions{})
|
||||
hosts, err := db.ListHostsInLabel(filter, l1.ID, kolide.HostListOptions{})
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, hosts, 3)
|
||||
}
|
||||
@ -408,11 +409,13 @@ func testListUniqueHostsInLabels(t *testing.T, db kolide.Datastore) {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
uniqueHosts, err := db.ListUniqueHostsInLabels([]uint{l1.ID, l2.ID})
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
|
||||
uniqueHosts, err := db.ListUniqueHostsInLabels(filter, []uint{l1.ID, l2.ID})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(hosts), len(uniqueHosts))
|
||||
|
||||
labels, err := db.ListLabels(kolide.ListOptions{})
|
||||
labels, err := db.ListLabels(filter, kolide.ListOptions{})
|
||||
require.Nil(t, err)
|
||||
require.Len(t, labels, 2)
|
||||
|
||||
|
@ -60,7 +60,7 @@ func (d *Datastore) Host(id uint) (*kolide.Host, error) {
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) ListHosts(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
d.mtx.Lock()
|
||||
defer d.mtx.Unlock()
|
||||
|
||||
@ -132,7 +132,7 @@ func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, err error) {
|
||||
func (d *Datastore) GenerateHostStatusStatistics(filter kolide.TeamFilter, now time.Time) (online, offline, mia, new uint, err error) {
|
||||
d.mtx.Lock()
|
||||
defer d.mtx.Unlock()
|
||||
|
||||
|
@ -115,7 +115,7 @@ func (d *Datastore) Label(lid uint) (*kolide.Label, error) {
|
||||
return label, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) ListLabels(opt kolide.ListOptions) ([]*kolide.Label, error) {
|
||||
func (d *Datastore) ListLabels(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error) {
|
||||
d.mtx.Lock()
|
||||
defer d.mtx.Unlock()
|
||||
// We need to sort by keys to provide reliable ordering
|
||||
@ -177,7 +177,7 @@ func (d *Datastore) SearchLabels(filter kolide.TeamFilter, query string, omit ..
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) ListHostsInLabel(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
var hosts []*kolide.Host
|
||||
|
||||
d.mtx.Lock()
|
||||
@ -192,7 +192,7 @@ func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*k
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) ListUniqueHostsInLabels(labels []uint) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) ListUniqueHostsInLabels(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error) {
|
||||
var hosts []*kolide.Host
|
||||
|
||||
labelSet := map[uint]bool{}
|
||||
|
@ -282,7 +282,7 @@ func (d *Datastore) Host(id uint) (*kolide.Host, error) {
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) ListHosts(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
sql := `SELECT
|
||||
h.id,
|
||||
h.osquery_host_id,
|
||||
@ -345,9 +345,10 @@ func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error
|
||||
`
|
||||
}
|
||||
|
||||
sql += `FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id)
|
||||
WHERE TRUE
|
||||
`
|
||||
sql += fmt.Sprintf(`FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id)
|
||||
WHERE TRUE AND %s
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
switch opt.StatusFilter {
|
||||
case "new":
|
||||
sql += "AND DATE_ADD(h.created_at, INTERVAL 1 DAY) >= ?"
|
||||
@ -388,19 +389,21 @@ func (d *Datastore) CleanupIncomingHosts(now time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, e error) {
|
||||
func (d *Datastore) GenerateHostStatusStatistics(filter kolide.TeamFilter, now time.Time) (online, offline, mia, new uint, e error) {
|
||||
// The logic in this function should remain synchronized with
|
||||
// host.Status and CountHostsInTargets
|
||||
|
||||
sqlStatement := fmt.Sprintf(`
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL 30 DAY) <= ? THEN 1 ELSE 0 END), 0) mia,
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) <= ? AND DATE_ADD(seen_time, INTERVAL 30 DAY) >= ? THEN 1 ELSE 0 END), 0) offline,
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) > ? THEN 1 ELSE 0 END), 0) online,
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(created_at, INTERVAL 1 DAY) >= ? THEN 1 ELSE 0 END), 0) new
|
||||
FROM hosts
|
||||
LIMIT 1;
|
||||
`, kolide.OnlineIntervalBuffer, kolide.OnlineIntervalBuffer)
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL 30 DAY) <= ? THEN 1 ELSE 0 END), 0) mia,
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) <= ? AND DATE_ADD(seen_time, INTERVAL 30 DAY) >= ? THEN 1 ELSE 0 END), 0) offline,
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) > ? THEN 1 ELSE 0 END), 0) online,
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(created_at, INTERVAL 1 DAY) >= ? THEN 1 ELSE 0 END), 0) new
|
||||
FROM hosts WHERE %s
|
||||
LIMIT 1;
|
||||
`, kolide.OnlineIntervalBuffer, kolide.OnlineIntervalBuffer,
|
||||
d.whereFilterHostsByTeams(filter, "hosts"),
|
||||
)
|
||||
|
||||
counts := struct {
|
||||
MIA uint `db:"mia"`
|
||||
@ -704,15 +707,16 @@ func (d *Datastore) SearchHosts(filter kolide.TeamFilter, query string, omit ...
|
||||
|
||||
}
|
||||
|
||||
func (d *Datastore) HostIDsByName(hostnames []string) ([]uint, error) {
|
||||
func (d *Datastore) HostIDsByName(filter kolide.TeamFilter, hostnames []string) ([]uint, error) {
|
||||
if len(hostnames) == 0 {
|
||||
return []uint{}, nil
|
||||
}
|
||||
|
||||
sqlStatement := `
|
||||
SELECT id FROM hosts
|
||||
WHERE host_name IN (?)
|
||||
`
|
||||
sqlStatement := fmt.Sprintf(`
|
||||
SELECT id FROM hosts
|
||||
WHERE host_name IN (?) AND %s
|
||||
`, d.whereFilterHostsByTeams(filter, "hosts"),
|
||||
)
|
||||
|
||||
sql, args, err := sqlx.In(sqlStatement, hostnames)
|
||||
if err != nil {
|
||||
|
@ -243,11 +243,14 @@ func (d *Datastore) Label(lid uint) (*kolide.Label, error) {
|
||||
}
|
||||
|
||||
// ListLabels returns all labels limited or sorted by kolide.ListOptions.
|
||||
func (d *Datastore) ListLabels(opt kolide.ListOptions) ([]*kolide.Label, error) {
|
||||
query := `
|
||||
SELECT *, (SELECT COUNT(1) FROM label_membership WHERE label_id = id) AS host_count
|
||||
FROM labels
|
||||
`
|
||||
func (d *Datastore) ListLabels(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error) {
|
||||
query := fmt.Sprintf(`
|
||||
SELECT *,
|
||||
(SELECT COUNT(1) FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id) WHERE label_id = l.id AND %s) AS host_count
|
||||
FROM labels l
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
|
||||
query = appendListOptionsToSQL(query, opt)
|
||||
labels := []*kolide.Label{}
|
||||
|
||||
@ -389,14 +392,16 @@ func (d *Datastore) ListLabelsForHost(hid uint) ([]*kolide.Label, error) {
|
||||
|
||||
// ListHostsInLabel returns a list of kolide.Host that are associated
|
||||
// with kolide.Label referened by Label ID
|
||||
func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
sql := `
|
||||
SELECT h.*
|
||||
FROM label_membership lm
|
||||
JOIN hosts h
|
||||
ON lm.host_id = h.id
|
||||
WHERE lm.label_id = ?
|
||||
`
|
||||
func (d *Datastore) ListHostsInLabel(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT h.*
|
||||
FROM label_membership lm
|
||||
JOIN hosts h
|
||||
ON lm.host_id = h.id
|
||||
WHERE lm.label_id = ? AND %s
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
|
||||
params := []interface{}{lid}
|
||||
|
||||
sql, params = searchLike(sql, params, opt.MatchQuery, hostSearchColumns...)
|
||||
@ -410,18 +415,20 @@ func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*k
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) ListUniqueHostsInLabels(labels []uint) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) ListUniqueHostsInLabels(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error) {
|
||||
if len(labels) == 0 {
|
||||
return []*kolide.Host{}, nil
|
||||
}
|
||||
|
||||
sqlStatement := `
|
||||
SELECT DISTINCT h.*
|
||||
FROM label_membership lm
|
||||
JOIN hosts h
|
||||
ON lm.host_id = h.id
|
||||
WHERE lm.label_id IN (?)
|
||||
`
|
||||
sqlStatement := fmt.Sprintf(`
|
||||
SELECT DISTINCT h.*
|
||||
FROM label_membership lm
|
||||
JOIN hosts h
|
||||
ON lm.host_id = h.id
|
||||
WHERE lm.label_id IN (?) AND %s
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
|
||||
query, args, err := sqlx.In(sqlStatement, labels)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "building query listing unique hosts in labels")
|
||||
|
@ -47,7 +47,7 @@ type HostStore interface {
|
||||
// provided host enrollment cooldown, by returning an error if the host has
|
||||
// enrolled within the cooldown period.
|
||||
EnrollHost(osqueryHostId, nodeKey string, teamID *uint, cooldown time.Duration) (*Host, error)
|
||||
ListHosts(opt HostListOptions) ([]*Host, error)
|
||||
ListHosts(filter TeamFilter, opt HostListOptions) ([]*Host, error)
|
||||
// AuthenticateHost authenticates and returns host metadata by node key.
|
||||
// This method should not return the host "additional" information as this
|
||||
// is not typically necessary for the operations performed by the osquery
|
||||
@ -66,9 +66,9 @@ type HostStore interface {
|
||||
CleanupIncomingHosts(now time.Time) error
|
||||
// GenerateHostStatusStatistics retrieves the count of online, offline,
|
||||
// MIA and new hosts.
|
||||
GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, err error)
|
||||
GenerateHostStatusStatistics(filter TeamFilter, now time.Time) (online, offline, mia, new uint, err error)
|
||||
// HostIDsByName Retrieve the IDs associated with the given hostnames
|
||||
HostIDsByName(hostnames []string) ([]uint, error)
|
||||
HostIDsByName(filter TeamFilter, hostnames []string) ([]uint, error)
|
||||
// HostByIdentifier returns one host matching the provided identifier.
|
||||
// Possible matches can be on osquery_host_identifier, node_key, UUID, or
|
||||
// hostname.
|
||||
|
@ -21,7 +21,7 @@ type LabelStore interface {
|
||||
SaveLabel(label *Label) (*Label, error)
|
||||
DeleteLabel(name string) error
|
||||
Label(lid uint) (*Label, error)
|
||||
ListLabels(opt ListOptions) ([]*Label, error)
|
||||
ListLabels(filter TeamFilter, opt ListOptions) ([]*Label, error)
|
||||
|
||||
// LabelQueriesForHost returns the label queries that should be executed
|
||||
// for the given host. The cutoff is the minimum timestamp a query
|
||||
@ -41,12 +41,12 @@ type LabelStore interface {
|
||||
|
||||
// ListHostsInLabel returns a slice of hosts in the label with the
|
||||
// given ID.
|
||||
ListHostsInLabel(lid uint, opt HostListOptions) ([]*Host, error)
|
||||
ListHostsInLabel(filter TeamFilter, lid uint, opt HostListOptions) ([]*Host, error)
|
||||
|
||||
// ListUniqueHostsInLabels returns a slice of all of the hosts in the
|
||||
// given label IDs. A host will only appear once in the results even if
|
||||
// it is in multiple of the provided labels.
|
||||
ListUniqueHostsInLabels(labels []uint) ([]*Host, error)
|
||||
ListUniqueHostsInLabels(filter TeamFilter, labels []uint) ([]*Host, error)
|
||||
|
||||
SearchLabels(filter TeamFilter, query string, omit ...uint) ([]*Label, error)
|
||||
|
||||
|
@ -20,7 +20,7 @@ type HostFunc func(id uint) (*kolide.Host, error)
|
||||
|
||||
type HostByIdentifierFunc func(identifier string) (*kolide.Host, error)
|
||||
|
||||
type ListHostsFunc func(opt kolide.HostListOptions) ([]*kolide.Host, error)
|
||||
type ListHostsFunc func(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error)
|
||||
|
||||
type EnrollHostFunc func(osqueryHostId, nodeKey string, teamID *uint, cooldown time.Duration) (*kolide.Host, error)
|
||||
|
||||
@ -34,11 +34,11 @@ type CleanupIncomingHostsFunc func(t time.Time) error
|
||||
|
||||
type SearchHostsFunc func(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error)
|
||||
|
||||
type GenerateHostStatusStatisticsFunc func(now time.Time) (online uint, offline uint, mia uint, new uint, err error)
|
||||
type GenerateHostStatusStatisticsFunc func(filter kolide.TeamFilter, now time.Time) (online uint, offline uint, mia uint, new uint, err error)
|
||||
|
||||
type DistributedQueriesForHostFunc func(host *kolide.Host) (map[uint]string, error)
|
||||
|
||||
type HostIDsByNameFunc func(hostnames []string) ([]uint, error)
|
||||
type HostIDsByNameFunc func(filter kolide.TeamFilter, hostnames []string) ([]uint, error)
|
||||
|
||||
type AddHostsToTeamFunc func(teamID *uint, hostIDs []uint) error
|
||||
|
||||
@ -122,9 +122,9 @@ func (s *HostStore) HostByIdentifier(identifier string) (*kolide.Host, error) {
|
||||
return s.HostByIdentifierFunc(identifier)
|
||||
}
|
||||
|
||||
func (s *HostStore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
func (s *HostStore) ListHosts(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
s.ListHostsFuncInvoked = true
|
||||
return s.ListHostsFunc(opt)
|
||||
return s.ListHostsFunc(filter, opt)
|
||||
}
|
||||
|
||||
func (s *HostStore) EnrollHost(osqueryHostId, nodeKey string, teamID *uint, cooldown time.Duration) (*kolide.Host, error) {
|
||||
@ -157,9 +157,9 @@ func (s *HostStore) SearchHosts(filter kolide.TeamFilter, query string, omit ...
|
||||
return s.SearchHostsFunc(filter, query, omit...)
|
||||
}
|
||||
|
||||
func (s *HostStore) GenerateHostStatusStatistics(now time.Time) (online uint, offline uint, mia uint, new uint, err error) {
|
||||
func (s *HostStore) GenerateHostStatusStatistics(filter kolide.TeamFilter, now time.Time) (online uint, offline uint, mia uint, new uint, err error) {
|
||||
s.GenerateHostStatusStatisticsFuncInvoked = true
|
||||
return s.GenerateHostStatusStatisticsFunc(now)
|
||||
return s.GenerateHostStatusStatisticsFunc(filter, now)
|
||||
}
|
||||
|
||||
func (s *HostStore) DistributedQueriesForHost(host *kolide.Host) (map[uint]string, error) {
|
||||
@ -167,9 +167,9 @@ func (s *HostStore) DistributedQueriesForHost(host *kolide.Host) (map[uint]strin
|
||||
return s.DistributedQueriesForHostFunc(host)
|
||||
}
|
||||
|
||||
func (s *HostStore) HostIDsByName(hostnames []string) ([]uint, error) {
|
||||
func (s *HostStore) HostIDsByName(filter kolide.TeamFilter, hostnames []string) ([]uint, error) {
|
||||
s.HostIDsByNameFuncInvoked = true
|
||||
return s.HostIDsByNameFunc(hostnames)
|
||||
return s.HostIDsByNameFunc(filter, hostnames)
|
||||
}
|
||||
|
||||
func (s *HostStore) AddHostsToTeam(teamID *uint, hostIDs []uint) error {
|
||||
|
@ -24,7 +24,7 @@ type DeleteLabelFunc func(name string) error
|
||||
|
||||
type LabelFunc func(lid uint) (*kolide.Label, error)
|
||||
|
||||
type ListLabelsFunc func(opt kolide.ListOptions) ([]*kolide.Label, error)
|
||||
type ListLabelsFunc func(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error)
|
||||
|
||||
type LabelQueriesForHostFunc func(host *kolide.Host, cutoff time.Time) (map[string]string, error)
|
||||
|
||||
@ -32,9 +32,9 @@ type RecordLabelQueryExecutionsFunc func(host *kolide.Host, results map[uint]boo
|
||||
|
||||
type ListLabelsForHostFunc func(hid uint) ([]*kolide.Label, error)
|
||||
|
||||
type ListHostsInLabelFunc func(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error)
|
||||
type ListHostsInLabelFunc func(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error)
|
||||
|
||||
type ListUniqueHostsInLabelsFunc func(labels []uint) ([]*kolide.Host, error)
|
||||
type ListUniqueHostsInLabelsFunc func(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error)
|
||||
|
||||
type SearchLabelsFunc func(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Label, error)
|
||||
|
||||
@ -122,9 +122,9 @@ func (s *LabelStore) Label(lid uint) (*kolide.Label, error) {
|
||||
return s.LabelFunc(lid)
|
||||
}
|
||||
|
||||
func (s *LabelStore) ListLabels(opt kolide.ListOptions) ([]*kolide.Label, error) {
|
||||
func (s *LabelStore) ListLabels(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error) {
|
||||
s.ListLabelsFuncInvoked = true
|
||||
return s.ListLabelsFunc(opt)
|
||||
return s.ListLabelsFunc(filter, opt)
|
||||
}
|
||||
|
||||
func (s *LabelStore) LabelQueriesForHost(host *kolide.Host, cutoff time.Time) (map[string]string, error) {
|
||||
@ -142,14 +142,14 @@ func (s *LabelStore) ListLabelsForHost(hid uint) ([]*kolide.Label, error) {
|
||||
return s.ListLabelsForHostFunc(hid)
|
||||
}
|
||||
|
||||
func (s *LabelStore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
func (s *LabelStore) ListHostsInLabel(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
s.ListHostsInLabelFuncInvoked = true
|
||||
return s.ListHostsInLabelFunc(lid, opt)
|
||||
return s.ListHostsInLabelFunc(filter, lid, opt)
|
||||
}
|
||||
|
||||
func (s *LabelStore) ListUniqueHostsInLabels(labels []uint) ([]*kolide.Host, error) {
|
||||
func (s *LabelStore) ListUniqueHostsInLabels(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error) {
|
||||
s.ListUniqueHostsInLabelsFuncInvoked = true
|
||||
return s.ListUniqueHostsInLabelsFunc(labels)
|
||||
return s.ListUniqueHostsInLabelsFunc(filter, labels)
|
||||
}
|
||||
|
||||
func (s *LabelStore) SearchLabels(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Label, error) {
|
||||
|
@ -16,7 +16,13 @@ import (
|
||||
)
|
||||
|
||||
func (svc Service) NewDistributedQueryCampaignByNames(ctx context.Context, queryString string, queryID *uint, hosts []string, labels []string) (*kolide.DistributedQueryCampaign, error) {
|
||||
hostIDs, err := svc.ds.HostIDsByName(hosts)
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, kolide.ErrNoContext
|
||||
}
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
|
||||
|
||||
hostIDs, err := svc.ds.HostIDsByName(filter, hosts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "finding host IDs")
|
||||
}
|
||||
@ -26,8 +32,6 @@ func (svc Service) NewDistributedQueryCampaignByNames(ctx context.Context, query
|
||||
return nil, errors.Wrap(err, "finding label IDs")
|
||||
}
|
||||
|
||||
// TODO handle teams
|
||||
|
||||
targets := kolide.HostTargets{HostIDs: hostIDs, LabelIDs: labelIDs}
|
||||
return svc.NewDistributedQueryCampaign(ctx, queryString, queryID, targets)
|
||||
}
|
||||
@ -69,6 +73,8 @@ func (svc Service) NewDistributedQueryCampaign(ctx context.Context, queryString
|
||||
return nil, errors.Wrap(err, "new query")
|
||||
}
|
||||
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: query.ObserverCanRun}
|
||||
|
||||
campaign, err := svc.ds.NewDistributedQueryCampaign(&kolide.DistributedQueryCampaign{
|
||||
QueryID: query.ID,
|
||||
Status: kolide.QueryWaiting,
|
||||
@ -114,8 +120,6 @@ func (svc Service) NewDistributedQueryCampaign(ctx context.Context, queryString
|
||||
}
|
||||
}
|
||||
|
||||
filter := kolide.TeamFilter{User: vc.User}
|
||||
|
||||
hostIDs, err := svc.ds.HostIDsInTargets(filter, targets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get target IDs")
|
||||
|
@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -12,7 +13,13 @@ func (svc Service) ListHosts(ctx context.Context, opt kolide.HostListOptions) ([
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc.ds.ListHosts(opt)
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, kolide.ErrNoContext
|
||||
}
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
|
||||
|
||||
return svc.ds.ListHosts(filter, opt)
|
||||
}
|
||||
|
||||
func (svc Service) GetHost(ctx context.Context, id uint) (*kolide.HostDetail, error) {
|
||||
@ -73,8 +80,13 @@ func (svc Service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, err
|
||||
if err := svc.authz.Authorize(ctx, &kolide.Host{}, kolide.ActionList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, kolide.ErrNoContext
|
||||
}
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
|
||||
|
||||
online, offline, mia, new, err := svc.ds.GenerateHostStatusStatistics(svc.clock.Now())
|
||||
online, offline, mia, new, err := svc.ds.GenerateHostStatusStatistics(filter, svc.clock.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -131,6 +143,11 @@ func (svc Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt
|
||||
if err := svc.authz.Authorize(ctx, &kolide.Team{}, kolide.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return kolide.ErrNoContext
|
||||
}
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
|
||||
|
||||
if opt.StatusFilter != "" && lid != nil {
|
||||
return kolide.NewInvalidArgumentError("status", "may not be provided with label_id")
|
||||
@ -142,9 +159,9 @@ func (svc Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt
|
||||
var hosts []*kolide.Host
|
||||
var err error
|
||||
if lid != nil {
|
||||
hosts, err = svc.ds.ListHostsInLabel(*lid, opt)
|
||||
hosts, err = svc.ds.ListHostsInLabel(filter, *lid, opt)
|
||||
} else {
|
||||
hosts, err = svc.ds.ListHosts(opt)
|
||||
hosts, err = svc.ds.ListHosts(filter, opt)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -48,7 +48,8 @@ func TestDeleteHost(t *testing.T) {
|
||||
err = svc.DeleteHost(test.UserContext(test.UserAdmin), host.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
hosts, err := ds.ListHosts(kolide.HostListOptions{})
|
||||
filter := kolide.TeamFilter{User: test.UserAdmin}
|
||||
hosts, err := ds.ListHosts(filter, kolide.HostListOptions{})
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, hosts, 0)
|
||||
|
||||
@ -113,7 +114,7 @@ func TestAddHostsToTeamByFilter(t *testing.T) {
|
||||
expectedHostIDs := []uint{1, 2, 4}
|
||||
expectedTeam := (*uint)(nil)
|
||||
|
||||
ds.ListHostsFunc = func(opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
ds.ListHostsFunc = func(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
var hosts []*kolide.Host
|
||||
for _, id := range expectedHostIDs {
|
||||
hosts = append(hosts, &kolide.Host{ID: id})
|
||||
@ -137,7 +138,7 @@ func TestAddHostsToTeamByFilterLabel(t *testing.T) {
|
||||
expectedTeam := ptr.Uint(1)
|
||||
expectedLabel := ptr.Uint(2)
|
||||
|
||||
ds.ListHostsInLabelFunc = func(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
ds.ListHostsInLabelFunc = func(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
assert.Equal(t, *expectedLabel, lid)
|
||||
var hosts []*kolide.Host
|
||||
for _, id := range expectedHostIDs {
|
||||
@ -157,7 +158,7 @@ func TestAddHostsToTeamByFilterEmptyHosts(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
svc := newTestService(ds, nil, nil)
|
||||
|
||||
ds.ListHostsFunc = func(opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
ds.ListHostsFunc = func(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
return []*kolide.Host{}, nil
|
||||
}
|
||||
ds.AddHostsToTeamFunc = func(teamID *uint, hostIDs []uint) error {
|
||||
|
@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -94,8 +95,13 @@ func (svc *Service) ListLabels(ctx context.Context, opt kolide.ListOptions) ([]*
|
||||
if err := svc.authz.Authorize(ctx, &kolide.Label{}, kolide.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, kolide.ErrNoContext
|
||||
}
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
|
||||
|
||||
return svc.ds.ListLabels(opt)
|
||||
return svc.ds.ListLabels(filter, opt)
|
||||
}
|
||||
|
||||
func (svc *Service) GetLabel(ctx context.Context, id uint) (*kolide.Label, error) {
|
||||
@ -130,8 +136,13 @@ func (svc *Service) ListHostsInLabel(ctx context.Context, lid uint, opt kolide.H
|
||||
if err := svc.authz.Authorize(ctx, &kolide.Label{}, kolide.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, kolide.ErrNoContext
|
||||
}
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
|
||||
|
||||
return svc.ds.ListHostsInLabel(lid, opt)
|
||||
return svc.ds.ListHostsInLabel(filter, lid, opt)
|
||||
}
|
||||
|
||||
func (svc *Service) ListLabelsForHost(ctx context.Context, hid uint) ([]*kolide.Label, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user