add team_id filter to fleetctl & api (#1596)

* add team_id filter to fleetctl via get hosts --team flag & api via api/v1/fleet/hosts and api/v1/fleet/labels/id/hosts

* update tests & add changes file
This commit is contained in:
Benjamin Edwards 2021-08-11 10:40:56 -04:00 committed by GitHub
parent 3b4bbf8f8e
commit 8bdad712d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 3 deletions

View file

@ -0,0 +1,2 @@
* add the `team_id` query parameter to `/api/v1/fleet/hosts` & `/api/v1/fleet/labels/{id}/hosts` which will filter the hosts by the specified team_id
* add a `--team` command line flag to fleetctl to allow filtering hosts by team i.e. `fleetctl get hosts --team=1`

View file

@ -561,6 +561,11 @@ func getHostsCommand() *cli.Command {
Aliases: []string{"host", "h"},
Usage: "List information about one or more hosts",
Flags: []cli.Flag{
&cli.UintFlag{
Name: "team",
Usage: "filter hosts by team_id",
Required: false,
},
jsonFlag(),
yamlFlag(),
configFlag(),
@ -576,7 +581,11 @@ func getHostsCommand() *cli.Command {
identifier := c.Args().First()
if identifier == "" {
hosts, err := client.GetHosts()
query := ""
if c.Uint("team") > 0 {
query = fmt.Sprintf("team_id=%d", c.Uint("team"))
}
hosts, err := client.GetHosts(query)
if err != nil {
return errors.Wrap(err, "could not list hosts")
}

View file

@ -345,6 +345,7 @@ func (d *Datastore) ListHosts(filter fleet.TeamFilter, opt fleet.HostListOptions
)
sql, params = filterHostsByStatus(sql, opt, params)
sql, params = filterHostsByTeam(sql, opt, params)
sql, params = searchLike(sql, params, opt.MatchQuery, hostSearchColumns...)
sql = appendListOptionsToSQL(sql, opt.ListOptions)
@ -357,6 +358,14 @@ func (d *Datastore) ListHosts(filter fleet.TeamFilter, opt fleet.HostListOptions
return hosts, nil
}
func filterHostsByTeam(sql string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) {
if opt.TeamFilter != nil {
sql += ` AND h.team_id = ?`
params = append(params, *opt.TeamFilter)
}
return sql, params
}
func filterHostsByStatus(sql string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) {
switch opt.StatusFilter {
case "new":

View file

@ -389,10 +389,31 @@ func TestListHostsQuery(t *testing.T) {
filter := fleet.TeamFilter{User: test.UserAdmin}
team1, err := ds.NewTeam(&fleet.Team{Name: "team1"})
require.NoError(t, err)
team2, err := ds.NewTeam(&fleet.Team{Name: "team2"})
require.NoError(t, err)
for _, host := range hosts {
require.NoError(t, ds.AddHostsToTeam(&team1.ID, []uint{host.ID}))
}
gotHosts, err := ds.ListHosts(filter, fleet.HostListOptions{})
require.Nil(t, err)
assert.Equal(t, len(hosts), len(gotHosts))
gotHosts, err = ds.ListHosts(filter, fleet.HostListOptions{TeamFilter: &team1.ID})
require.NoError(t, err)
assert.Equal(t, len(hosts), len(gotHosts))
gotHosts, err = ds.ListHosts(filter, fleet.HostListOptions{TeamFilter: &team2.ID})
require.NoError(t, err)
assert.Equal(t, 0, len(gotHosts))
gotHosts, err = ds.ListHosts(filter, fleet.HostListOptions{TeamFilter: nil})
require.NoError(t, err)
assert.Equal(t, len(hosts), len(gotHosts))
gotHosts, err = ds.ListHosts(filter, fleet.HostListOptions{ListOptions: fleet.ListOptions{MatchQuery: "00"}})
require.Nil(t, err)
assert.Equal(t, 10, len(gotHosts))

View file

@ -413,6 +413,7 @@ func (d *Datastore) ListHostsInLabel(filter fleet.TeamFilter, lid uint, opt flee
params := []interface{}{lid}
sql, params = filterHostsByStatus(sql, opt, params)
sql, params = filterHostsByTeam(sql, opt, params)
sql, params = searchLike(sql, params, opt.MatchQuery, hostSearchColumns...)
sql = appendListOptionsToSQL(sql, opt.ListOptions)

View file

@ -388,6 +388,83 @@ func TestListHostsInLabelAndStatus(t *testing.T) {
}
}
func TestListHostsInLabelAndTeamFilter(t *testing.T) {
db := CreateMySQLDS(t)
defer db.Close()
h1, err := db.NewHost(&fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
SeenTime: time.Now(),
OsqueryHostID: "1",
NodeKey: "1",
UUID: "1",
Hostname: "foo.local",
})
require.Nil(t, err)
lastSeenTime := time.Now().Add(-1000 * time.Hour)
h2, err := db.NewHost(&fleet.Host{
DetailUpdatedAt: lastSeenTime,
LabelUpdatedAt: lastSeenTime,
SeenTime: lastSeenTime,
OsqueryHostID: "2",
NodeKey: "2",
UUID: "2",
Hostname: "bar.local",
})
require.Nil(t, err)
l1 := &fleet.LabelSpec{
ID: 1,
Name: "label foo",
Query: "query1",
}
err = db.ApplyLabelSpecs([]*fleet.LabelSpec{l1})
require.Nil(t, err)
team1, err := db.NewTeam(&fleet.Team{Name: "team1"})
require.NoError(t, err)
team2, err := db.NewTeam(&fleet.Team{Name: "team2"})
require.NoError(t, err)
db.AddHostsToTeam(&team1.ID, []uint{h1.ID})
filter := fleet.TeamFilter{User: test.UserAdmin}
for _, h := range []*fleet.Host{h1, h2} {
err = db.RecordLabelQueryExecutions(h, map[uint]bool{l1.ID: true}, time.Now())
assert.Nil(t, err)
}
{
hosts, err := db.ListHostsInLabel(filter, l1.ID, fleet.HostListOptions{StatusFilter: fleet.StatusOnline})
require.Nil(t, err)
require.Len(t, hosts, 1)
assert.Equal(t, "foo.local", hosts[0].Hostname)
}
{
hosts, err := db.ListHostsInLabel(filter, l1.ID, fleet.HostListOptions{StatusFilter: fleet.StatusMIA})
require.Nil(t, err)
require.Len(t, hosts, 1)
assert.Equal(t, "bar.local", hosts[0].Hostname)
}
{
hosts, err := db.ListHostsInLabel(filter, l1.ID, fleet.HostListOptions{TeamFilter: &team1.ID})
require.Nil(t, err)
require.Len(t, hosts, 1)
assert.Equal(t, "foo.local", hosts[0].Hostname)
}
{
hosts, err := db.ListHostsInLabel(filter, l1.ID, fleet.HostListOptions{TeamFilter: &team2.ID})
require.Nil(t, err)
require.Len(t, hosts, 0)
}
}
func TestBuiltInLabels(t *testing.T) {
db := CreateMySQLDS(t)
defer db.Close()

View file

@ -109,6 +109,8 @@ type HostListOptions struct {
AdditionalFilters []string
// StatusFilter selects the online status of the hosts.
StatusFilter HostStatus
// TeamFilter selects the hosts for specified team
TeamFilter *uint
}
type HostUser struct {

View file

@ -11,8 +11,8 @@ import (
)
// GetHosts retrieves the list of all Hosts
func (c *Client) GetHosts() ([]HostResponse, error) {
response, err := c.AuthenticatedDo("GET", "/api/v1/fleet/hosts", "", nil)
func (c *Client) GetHosts(query string) ([]HostResponse, error) {
response, err := c.AuthenticatedDo("GET", "/api/v1/fleet/hosts", query, nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/fleet/hosts")
}

View file

@ -183,6 +183,16 @@ func hostListOptionsFromRequest(r *http.Request) (fleet.HostListOptions, error)
hopt.AdditionalFilters = strings.Split(additionalInfoFiltersString, ",")
}
team_id := r.URL.Query().Get("team_id")
if team_id != "" {
id, err := strconv.Atoi(team_id)
if err != nil {
return hopt, err
}
tid := uint(id)
hopt.TeamFilter = &tid
}
return hopt, nil
}