diff --git a/changes/issue-3979-filter-hosts-targets b/changes/issue-3979-filter-hosts-targets new file mode 100644 index 0000000000..42b3b4c586 --- /dev/null +++ b/changes/issue-3979-filter-hosts-targets @@ -0,0 +1 @@ +* Make target search behave the same as host search diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 8fe06a0ca7..62f14c8266 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -771,33 +771,19 @@ func (ds *Datastore) MarkHostsSeen(ctx context.Context, hostIDs []uint, t time.T // SearchHosts performs a search on the hosts table using the following criteria: // - Use the provided team filter. -// - Full-text search with the "query" argument (if query == "", then no fulltext matching is executed). -// Full-text search is used even if "query" is a short or stopword. -// (what defines a short word is the "ft_min_word_len" VARIABLE, set to 4 by default in Fleet deployments). +// - Search hostname, uuid, hardware_serial, and primary_ip using LIKE (mimics ListHosts behavior) // - An optional list of IDs to omit from the search. -func (ds *Datastore) SearchHosts(ctx context.Context, filter fleet.TeamFilter, query string, omit ...uint) ([]*fleet.Host, error) { - var sqlb strings.Builder - sqlb.WriteString(`SELECT +func (ds *Datastore) SearchHosts(ctx context.Context, filter fleet.TeamFilter, matchQuery string, omit ...uint) ([]*fleet.Host, error) { + query := `SELECT h.*, COALESCE(hst.seen_time, h.created_at) AS seen_time FROM hosts h LEFT JOIN host_seen_times hst - ON (h.id=hst.host_id) WHERE`) + ON (h.id=hst.host_id) WHERE TRUE ` var args []interface{} - if len(query) > 0 { - sqlb.WriteString(` ( - MATCH (hostname, uuid) AGAINST (? IN BOOLEAN MODE) - OR MATCH (primary_ip, primary_mac) AGAINST (? IN BOOLEAN MODE) - ) AND`) - // Transform query argument and append the truncation operator "*" for MATCH. - // From Oracle docs: "If a word is specified with the truncation operator, it is not - // stripped from a boolean query, even if it is too short or a stopword." - hostQuery := transformQueryWithSuffix(query, "*") - // Needs quotes to avoid each "." marking a word boundary. - // TODO(lucas): Currently matching the primary_mac doesn't work, see #1959. - ipQuery := `"` + query + `"` - args = append(args, hostQuery, ipQuery) + if len(matchQuery) > 0 { + query, args = hostSearchLike(query, args, matchQuery, hostSearchColumns...) } var in interface{} // use -1 if there are no values to omit. @@ -807,17 +793,17 @@ func (ds *Datastore) SearchHosts(ctx context.Context, filter fleet.TeamFilter, q in = -1 } args = append(args, in) - sqlb.WriteString(" id NOT IN (?) AND ") - sqlb.WriteString(ds.whereFilterHostsByTeams(filter, "h")) - sqlb.WriteString(` ORDER BY h.id DESC LIMIT 10`) + query += " AND id NOT IN (?) AND " + query += ds.whereFilterHostsByTeams(filter, "h") + query += ` ORDER BY h.id DESC LIMIT 10` - sql, args, err := sqlx.In(sqlb.String(), args...) + query, args, err := sqlx.In(query, args...) if err != nil { return nil, ctxerr.Wrap(ctx, err, "searching default hosts") } - sql = ds.reader.Rebind(sql) + query = ds.reader.Rebind(query) hosts := []*fleet.Host{} - if err := sqlx.SelectContext(ctx, ds.reader, &hosts, sql, args...); err != nil { + if err := sqlx.SelectContext(ctx, ds.reader, &hosts, query, args...); err != nil { return nil, ctxerr.Wrap(ctx, err, "searching hosts") } return hosts, nil diff --git a/server/datastore/mysql/hosts_test.go b/server/datastore/mysql/hosts_test.go index c265e5e5ef..c41619ae54 100644 --- a/server/datastore/mysql/hosts_test.go +++ b/server/datastore/mysql/hosts_test.go @@ -877,7 +877,7 @@ func testHostsSearch(t *testing.T, ds *Datastore) { SeenTime: time.Now(), NodeKey: "1", UUID: "1", - Hostname: "foo.local", + Hostname: "fo.local", }) require.NoError(t, err) @@ -923,19 +923,23 @@ func testHostsSearch(t *testing.T, ds *Datastore) { _, err = ds.SearchHosts(context.Background(), filter, "") require.NoError(t, err) - hosts, err := ds.SearchHosts(context.Background(), filter, "foo") + hosts, err := ds.SearchHosts(context.Background(), filter, "fo") require.NoError(t, err) assert.Len(t, hosts, 2) - host, err := ds.SearchHosts(context.Background(), filter, "foo", h3.ID) + hosts, err = ds.SearchHosts(context.Background(), filter, "fo.") require.NoError(t, err) - require.Len(t, host, 1) - assert.Equal(t, "foo.local", host[0].Hostname) + assert.Len(t, hosts, 1) - host, err = ds.SearchHosts(context.Background(), filter, "foo", h3.ID, h2.ID) + host, err := ds.SearchHosts(context.Background(), filter, "fo", h3.ID) require.NoError(t, err) require.Len(t, host, 1) - assert.Equal(t, "foo.local", host[0].Hostname) + assert.Equal(t, "fo.local", host[0].Hostname) + + host, err = ds.SearchHosts(context.Background(), filter, "fo", h3.ID, h2.ID) + require.NoError(t, err) + require.Len(t, host, 1) + assert.Equal(t, "fo.local", host[0].Hostname) host, err = ds.SearchHosts(context.Background(), filter, "abc") require.NoError(t, err)