Remove unused windows_updates MySQL table and ingestion (#44128)

**Related issue:** Resolves #44127

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Removed the unused Windows Updates feature: ingestion, parsing,
persistence APIs, and detail query; added a migration to drop the
related database table.
* **Tests**
* Removed unit and integration tests for Windows update parsing,
ingestion, persistence, and query inclusion.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Lucas Manuel Rodriguez
2026-04-24 15:21:34 -03:00
committed by GitHub
parent 6c28db8206
commit c22954edf2
15 changed files with 29 additions and 479 deletions
@@ -0,0 +1 @@
* Removed unused `windows_updates` MySQL table and ingestion code.
@@ -1304,20 +1304,6 @@ WITH cached_groups AS (select * from groups)
SELECT uid, username, email FROM users
```
## windows_update_history
- Platforms: windows
- Discovery query:
```sql
SELECT 1 FROM osquery_registry WHERE active = true AND registry = 'table' AND name = 'windows_update_history'
```
- Query:
```sql
SELECT date, title FROM windows_update_history WHERE result_code = 'Succeeded'
```
<br /><br />[^1]: Software override queries write over the default queries. They are used to populate the software inventory.
<meta name="navSection" value="Dig deeper">
<meta name="pageOrderInSection" value="1600">
-1
View File
@@ -583,7 +583,6 @@ var hostRefs = []string{
"host_orbit_info",
"host_munki_issues",
"host_display_names",
"windows_updates",
"host_disks",
"host_updates",
"host_disk_encryption_keys",
-4
View File
@@ -8985,10 +8985,6 @@ func testHostsDeleteHosts(t *testing.T, ds *Datastore) {
// Update host_operating_system
err = ds.UpdateHostOperatingSystem(context.Background(), host.ID, fleet.OperatingSystem{Name: "foo", Version: "bar"})
require.NoError(t, err)
// Insert a windows update for the host
stmt := `INSERT INTO windows_updates (host_id, date_epoch, kb_id) VALUES (?, ?, ?)`
_, err = ds.writer(context.Background()).Exec(stmt, host.ID, 1, 123)
require.NoError(t, err)
// set host' disk space
err = ds.SetOrUpdateHostDisksSpace(context.Background(), host.ID, 12, 25, 40.0, nil)
require.NoError(t, err)
@@ -0,0 +1,21 @@
package tables
import (
"database/sql"
"fmt"
)
func init() {
MigrationClient.AddMigration(Up_20260423161824, Down_20260423161824)
}
func Up_20260423161824(tx *sql.Tx) error {
if _, err := tx.Exec(`DROP TABLE IF EXISTS windows_updates`); err != nil {
return fmt.Errorf("drop windows_updates table: %w", err)
}
return nil
}
func Down_20260423161824(tx *sql.Tx) error {
return nil
}
File diff suppressed because one or more lines are too long
-74
View File
@@ -1,74 +0,0 @@
package mysql
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/jmoiron/sqlx"
)
func (ds *Datastore) ListWindowsUpdatesByHostID(
ctx context.Context,
hostID uint,
) ([]fleet.WindowsUpdate, error) {
stmt := `
SELECT kb_id, date_epoch
FROM windows_updates wu
WHERE host_id = ?
ORDER BY date_epoch
`
updates := []fleet.WindowsUpdate{}
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &updates, stmt, hostID); err != nil {
return nil, ctxerr.Wrap(ctx, err, "list windows updates")
}
return updates, nil
}
// InsertWindowsUpdates inserts one or more windows updates for the given host.
func (ds *Datastore) InsertWindowsUpdates(ctx context.Context, hostID uint, updates []fleet.WindowsUpdate) error {
if len(updates) == 0 {
return nil
}
// The windows_updates_history table in OSQUERY is append only so we only need to figure what
// new updates were installed since the last sync.
var lastUpdateEpoch uint
var args []interface{}
var placeholders []string
lastUpdateSmt := `SELECT date_epoch FROM windows_updates WHERE host_id = ? ORDER BY date_epoch DESC LIMIT 1`
if err := sqlx.GetContext(ctx, ds.reader(ctx), &lastUpdateEpoch, lastUpdateSmt, hostID); err != nil {
if err != sql.ErrNoRows {
return ctxerr.Wrap(ctx, err, "inserting windows updates")
}
lastUpdateEpoch = 0
}
for _, v := range updates {
if v.DateEpoch > lastUpdateEpoch {
placeholders = append(placeholders, "(?,?,?)")
args = append(args, hostID, v.DateEpoch, v.KBID)
}
}
if len(args) > 0 {
smt := fmt.Sprintf(
`INSERT IGNORE INTO windows_updates (host_id, date_epoch, kb_id) VALUES %s`,
strings.Join(placeholders, ","),
)
if _, err := ds.writer(ctx).ExecContext(ctx, smt, args...); err != nil {
return ctxerr.Wrap(ctx, err, "inserting windows updates")
}
}
return nil
}
@@ -1,113 +0,0 @@
package mysql
import (
"context"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
func TestWindowsUpdates(t *testing.T) {
ds := CreateMySQLDS(t)
cases := []struct {
name string
fn func(t *testing.T, ds *Datastore)
}{
{"InsertWindowsUpdates", testInsertWindowsUpdates},
{"ListWindowsUpdatesByHostID", testListWindowsUpdatesByHostID},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer TruncateTables(t, ds)
c.fn(t, ds)
})
}
}
func testListWindowsUpdatesByHostID(t *testing.T, ds *Datastore) {
ctx := context.Background()
now := uint(time.Now().Unix()) //nolint:gosec // dismiss G115
t.Run("with no stored updates", func(t *testing.T) {
actual, err := ds.ListWindowsUpdatesByHostID(ctx, 1)
require.NoError(t, err)
require.Empty(t, actual)
})
t.Run("none matching", func(t *testing.T) {
updates := []fleet.WindowsUpdate{
{KBID: 1, DateEpoch: now},
{KBID: 2, DateEpoch: now + 1},
}
err := ds.InsertWindowsUpdates(ctx, 1, updates)
require.NoError(t, err)
actual, err := ds.ListWindowsUpdatesByHostID(ctx, 2)
require.NoError(t, err)
require.Empty(t, actual)
})
t.Run("returns matching", func(t *testing.T) {
expected := []fleet.WindowsUpdate{
{KBID: 1, DateEpoch: now},
{KBID: 2, DateEpoch: now + 1},
}
err := ds.InsertWindowsUpdates(ctx, 1, expected)
require.NoError(t, err)
actual, err := ds.ListWindowsUpdatesByHostID(ctx, 1)
require.NoError(t, err)
require.ElementsMatch(t, expected, actual)
})
}
func testInsertWindowsUpdates(t *testing.T, ds *Datastore) {
ctx := context.Background()
now := uint(time.Now().Unix()) //nolint:gosec // dismiss G115
smt := `SELECT kb_id, date_epoch FROM windows_updates WHERE host_id = ?`
t.Run("with no stored updates", func(t *testing.T) {
hostID := 1
updates := []fleet.WindowsUpdate{
{KBID: 1, DateEpoch: now},
{KBID: 2, DateEpoch: now + 1},
}
err := ds.InsertWindowsUpdates(ctx, 1, updates)
require.NoError(t, err)
var actual []fleet.WindowsUpdate
err = sqlx.SelectContext(ctx, ds.reader(ctx), &actual, smt, hostID)
require.NoError(t, err)
require.ElementsMatch(t, updates, actual)
})
t.Run("with stored updates", func(t *testing.T) {
hostID := 1
updates := []fleet.WindowsUpdate{
{KBID: 1, DateEpoch: now},
{KBID: 2, DateEpoch: now + 1},
}
err := ds.InsertWindowsUpdates(ctx, 1, updates)
require.NoError(t, err)
updates = append(updates, fleet.WindowsUpdate{KBID: 3, DateEpoch: now + 2})
err = ds.InsertWindowsUpdates(ctx, 1, updates)
require.NoError(t, err)
var actual []fleet.WindowsUpdate
err = sqlx.SelectContext(ctx, ds.reader(ctx), &actual, smt, hostID)
require.NoError(t, err)
require.ElementsMatch(t, updates, actual)
})
}
-4
View File
@@ -1251,10 +1251,6 @@ type Datastore interface {
InnoDBStatus(ctx context.Context) (string, error)
ProcessList(ctx context.Context) ([]MySQLProcess, error)
// WindowsUpdates Store
ListWindowsUpdatesByHostID(ctx context.Context, hostID uint) ([]WindowsUpdate, error)
InsertWindowsUpdates(ctx context.Context, hostID uint, updates []WindowsUpdate) error
///////////////////////////////////////////////////////////////////////////////
// OperatingSystemVulnerabilities Store
ListOSVulnerabilitiesByOS(ctx context.Context, osID uint) ([]OSVulnerability, error)
-78
View File
@@ -1,78 +0,0 @@
package fleet
import (
"fmt"
"regexp"
"strconv"
)
type WindowsUpdate struct {
KBID uint `db:"kb_id"`
DateEpoch uint `db:"date_epoch"`
}
// NewWindowsUpdate returns a new WindowsUpdate from the provided props:
// - title: The title of the windows update (see
// https://osquery.io/schema/5.4.0/#windows_update_history)
// - dateEpoch: The date the update was applied on (see
// https://osquery.io/schema/5.4.0/#windows_update_history)
func NewWindowsUpdate(title string, dateEpoch string) (WindowsUpdate, error) {
kbID, err := parseKBID(title)
if err != nil {
return WindowsUpdate{}, err
}
dEpoch, err := parseDateEpoch(dateEpoch)
if err != nil {
return WindowsUpdate{}, err
}
return WindowsUpdate{
KBID: kbID,
DateEpoch: dEpoch,
}, nil
}
func (wu WindowsUpdate) MoreRecent(other WindowsUpdate) bool {
return wu.DateEpoch > other.DateEpoch
}
func parseDateEpoch(val string) (uint, error) {
dEpoch, err := strconv.Atoi(val)
if err != nil {
return 0, err
}
if dEpoch < 0 {
return 0, fmt.Errorf("invalid epoch value %d", dEpoch)
}
return uint(dEpoch), nil
}
// parseKBID extracts the KB (Knowledge Base) id contained inside a string. KB ids are found based on
// the pattern 'KB\d+'. In case of multiple matches, the id
// will be based on the last match. Will return an error if:
// - No matches are found
// - The matched KB contains an 'invalid' id (< 0)
func parseKBID(str string) (uint, error) {
r := regexp.MustCompile(`\s?\(?KB(?P<Id>\d+)\s?\)?`)
m := r.FindAllStringSubmatch(str, -1)
idx := r.SubexpIndex("Id")
if len(m) == 0 || idx <= 0 {
return 0, fmt.Errorf("KB id not found in %s", str)
}
last := m[len(m)-1]
id, err := strconv.Atoi(last[idx])
if err != nil {
return 0, err
}
if id <= 0 {
return 0, fmt.Errorf("Invalid KB id value found in %s", str)
}
return uint(id), nil
}
-57
View File
@@ -1,57 +0,0 @@
package fleet
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseKBId(t *testing.T) {
testCases := []struct {
input string
expected uint
errors bool
}{
{
input: "2022-04 Update for Windows 10 Version 21H2 for x64-based Systems based on (KB2267602) based on KB2267601 (KB5005463)",
expected: 5005463,
errors: false,
},
{
input: "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.1239.0)",
expected: 2267602,
errors: false,
},
{
input: "2022-04 Update for Windows 10 Version 21H2 for x64-based Systems (KB5005463)",
expected: 5005463,
errors: false,
},
{
input: "2022-04 Update for Windows 10 Version 21H2 for x64-based Systems (KB-5005463)",
expected: 0,
errors: true,
},
{
input: "2022-04 Update for Windows 10 Version 21H2 for x64-based Systems (KB0)",
expected: 0,
errors: true,
},
{
input: "Some random string",
expected: 0,
errors: true,
},
}
for _, tCase := range testCases {
actual, err := parseKBID(tCase.input)
require.Equal(t, tCase.expected, actual)
if !tCase.errors {
require.NoError(t, err)
} else {
require.Error(t, err)
}
}
}
-24
View File
@@ -887,10 +887,6 @@ type InnoDBStatusFunc func(ctx context.Context) (string, error)
type ProcessListFunc func(ctx context.Context) ([]fleet.MySQLProcess, error)
type ListWindowsUpdatesByHostIDFunc func(ctx context.Context, hostID uint) ([]fleet.WindowsUpdate, error)
type InsertWindowsUpdatesFunc func(ctx context.Context, hostID uint, updates []fleet.WindowsUpdate) error
type ListOSVulnerabilitiesByOSFunc func(ctx context.Context, osID uint) ([]fleet.OSVulnerability, error)
type ListVulnsByOsNameAndVersionFunc func(ctx context.Context, name string, version string, includeCVSS bool, teamID *uint, maxVulnerabilities *int) (fleet.OSVulnerabilitiesWithCount, error)
@@ -3178,12 +3174,6 @@ type DataStore struct {
ProcessListFunc ProcessListFunc
ProcessListFuncInvoked bool
ListWindowsUpdatesByHostIDFunc ListWindowsUpdatesByHostIDFunc
ListWindowsUpdatesByHostIDFuncInvoked bool
InsertWindowsUpdatesFunc InsertWindowsUpdatesFunc
InsertWindowsUpdatesFuncInvoked bool
ListOSVulnerabilitiesByOSFunc ListOSVulnerabilitiesByOSFunc
ListOSVulnerabilitiesByOSFuncInvoked bool
@@ -7696,20 +7686,6 @@ func (s *DataStore) ProcessList(ctx context.Context) ([]fleet.MySQLProcess, erro
return s.ProcessListFunc(ctx)
}
func (s *DataStore) ListWindowsUpdatesByHostID(ctx context.Context, hostID uint) ([]fleet.WindowsUpdate, error) {
s.mu.Lock()
s.ListWindowsUpdatesByHostIDFuncInvoked = true
s.mu.Unlock()
return s.ListWindowsUpdatesByHostIDFunc(ctx, hostID)
}
func (s *DataStore) InsertWindowsUpdates(ctx context.Context, hostID uint, updates []fleet.WindowsUpdate) error {
s.mu.Lock()
s.InsertWindowsUpdatesFuncInvoked = true
s.mu.Unlock()
return s.InsertWindowsUpdatesFunc(ctx, hostID, updates)
}
func (s *DataStore) ListOSVulnerabilitiesByOS(ctx context.Context, osID uint) ([]fleet.OSVulnerability, error) {
s.mu.Lock()
s.ListOSVulnerabilitiesByOSFuncInvoked = true
+5 -6
View File
@@ -1189,7 +1189,6 @@ func verifyDiscovery(t *testing.T, queries, discovery map[string]string) {
hostDetailQueryPrefix + "google_chrome_profiles": {},
hostDetailQueryPrefix + "mdm": {},
hostDetailQueryPrefix + "munki_info": {},
hostDetailQueryPrefix + "windows_update_history": {},
hostDetailQueryPrefix + "kubequery_info": {},
hostDetailQueryPrefix + "orbit_info": {},
hostDetailQueryPrefix + "software_vscode_extensions": {},
@@ -1715,8 +1714,8 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
// queries)
queries, discovery, acc, err := svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// +1 due to 'windows_update_history', +1 due to fleet_no_policies_wildcard query.
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Equal(t, len(expected)+1+1, len(queries)) {
// +1 due to fleet_no_policies_wildcard query.
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Len(t, expected, len(queries)-1) {
// this is just to print the diff between the expected and actual query
// keys when the count assertion fails, to help debugging - they are not
// expected to match.
@@ -1952,7 +1951,7 @@ func TestDetailQueries(t *testing.T) {
queries, discovery, acc, err := svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// +1 for fleet_no_policies_wildcard
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Equal(t, len(expected)+1, len(queries)) {
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Len(t, expected, len(queries)-1) {
// this is just to print the diff between the expected and actual query
// keys when the count assertion fails, to help debugging - they are not
// expected to match.
@@ -2493,8 +2492,8 @@ func TestDistributedQueryResults(t *testing.T) {
// Now we should get the active distributed query
queries, discovery, acc, err := svc.GetDistributedQueries(hostCtx)
require.NoError(t, err)
// +1 for the distributed query for campaign ID 42, +1 for windows update history, +1 for the fleet_no_policies_wildcard query.
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Equal(t, len(expected)+3, len(queries)) {
// +1 for the distributed query for campaign ID 42, +1 for the fleet_no_policies_wildcard query.
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Len(t, expected, len(queries)-2) {
// this is just to print the diff between the expected and actual query
// keys when the count assertion fails, to help debugging - they are not
// expected to match.
-47
View File
@@ -988,13 +988,6 @@ func withCachedUsers(query string) string {
return fmt.Sprintf(query, usersQueryStr)
}
var windowsUpdateHistory = DetailQuery{
Query: `SELECT date, title FROM windows_update_history WHERE result_code = 'Succeeded'`,
Platforms: []string{"windows"},
Discovery: discoveryTable("windows_update_history"),
DirectIngestFunc: directIngestWindowsUpdateHistory,
}
// macOSEntraIDDetails holds the query and ingestion function for macOS for Microsoft "Conditional access" feature.
var macOSEntraIDDetails = DetailQuery{
// The query ingests Entra's Device ID and User Principal Name of the account
@@ -2079,42 +2072,6 @@ func generateBatteryHealth(ctx context.Context, row map[string]string, logger *s
return batteryStatusGood, count, nil
}
func directIngestWindowsUpdateHistory(
ctx context.Context,
logger *slog.Logger,
host *fleet.Host,
ds fleet.Datastore,
rows []map[string]string,
) error {
// The windows update history table will also contain entries for the Defender Antivirus. Unfortunately
// there's no reliable way to differentiate between those entries and Cumulative OS updates.
// Since each antivirus update will have the same KB ID, but different 'dates', to
// avoid trying to insert duplicated data, we group by KB ID and then take the most 'out of
// date' update in each group.
uniq := make(map[uint]fleet.WindowsUpdate)
for _, row := range rows {
u, err := fleet.NewWindowsUpdate(row["title"], row["date"])
if err != nil {
// If the update failed to parse then we log a debug error and ignore it.
// E.g. we've seen KB updates with titles like "Logitech - Image - 1.4.40.0".
logger.DebugContext(ctx, "directIngestWindowsUpdateHistory skipped", "err", err)
continue
}
if v, ok := uniq[u.KBID]; !ok || v.MoreRecent(u) {
uniq[u.KBID] = u
}
}
var updates []fleet.WindowsUpdate
for _, v := range uniq {
updates = append(updates, v)
}
return ds.InsertWindowsUpdates(ctx, host.ID, updates)
}
func directIngestEntraIDDetails(
ctx context.Context,
logger *slog.Logger,
@@ -3378,10 +3335,6 @@ func GetDetailQueries(
generatedMap["users_chrome"] = usersQueryChrome
}
if !fleetConfig.Vulnerabilities.DisableWinOSVulnerabilities {
generatedMap["windows_update_history"] = windowsUpdateHistory
}
if fleetConfig.App.EnableScheduledQueryStats {
generatedMap["scheduled_query_stats"] = scheduledQueryStats
}
@@ -505,7 +505,6 @@ func TestGetDetailQueries(t *testing.T) {
"os_windows",
"os_unix_like",
"os_chrome",
"windows_update_history",
"kubequery_info",
"orbit_info",
"disk_encryption_darwin",
@@ -519,9 +518,6 @@ func TestGetDetailQueries(t *testing.T) {
require.Len(t, queriesNoConfig, len(baseQueries))
sortedKeysCompare(t, queriesNoConfig, baseQueries)
queriesWithoutWinOSVuln := GetDetailQueries(t.Context(), config.FleetConfig{Vulnerabilities: config.VulnerabilitiesConfig{DisableWinOSVulnerabilities: true}}, nil, nil, Integrations{}, nil)
require.Len(t, queriesWithoutWinOSVuln, 29)
queriesWithUsers := GetDetailQueries(t.Context(), config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, nil, &fleet.Features{EnableHostUsers: true}, Integrations{}, nil)
qs := baseQueries
qs = append(qs, "users", "users_chrome", "scheduled_query_stats")
@@ -1922,45 +1918,6 @@ func TestDirectIngestSoftware(t *testing.T) {
})
}
func TestDirectIngestWindowsUpdateHistory(t *testing.T) {
ds := new(mock.Store)
ds.InsertWindowsUpdatesFunc = func(ctx context.Context, hostID uint, updates []fleet.WindowsUpdate) error {
require.Len(t, updates, 6)
require.ElementsMatch(t, []fleet.WindowsUpdate{
{KBID: 2267602, DateEpoch: 1657929207},
{KBID: 890830, DateEpoch: 1658226954},
{KBID: 5013887, DateEpoch: 1658225364},
{KBID: 5005463, DateEpoch: 1658225225},
{KBID: 5010472, DateEpoch: 1658224963},
{KBID: 4052623, DateEpoch: 1657929544},
}, updates)
return nil
}
host := fleet.Host{
ID: 1,
}
payload := []map[string]string{
{"date": "1659392951", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.1239.0)"},
{"date": "1658271402", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.442.0)"},
{"date": "1658228495", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.415.0)"},
{"date": "1658226954", "title": "Windows Malicious Software Removal Tool x64 - v5.103 (KB890830)"},
{"date": "1658225364", "title": "2022-06 Cumulative Update for .NET Framework 3.5 and 4.8 for Windows 10 Version 21H2 for x64 (KB5013887)"},
{"date": "1658225225", "title": "2022-04 Update for Windows 10 Version 21H2 for x64-based Systems (KB5005463)"},
{"date": "1658224963", "title": "2022-02 Cumulative Update Preview for .NET Framework 3.5 and 4.8 for Windows 10 Version 21H2 for x64 (KB5010472)"},
{"date": "1658222131", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.400.0)"},
{"date": "1658189063", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.376.0)"},
{"date": "1658185542", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.386.0)"},
{"date": "1657929544", "title": "Update for Microsoft Defender Antivirus antimalware platform - KB4052623 (Version 4.18.2205.7)"},
{"date": "1657929207", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.203.0)"},
}
err := directIngestWindowsUpdateHistory(t.Context(), slog.New(slog.DiscardHandler), &host, ds, payload)
require.NoError(t, err)
require.True(t, ds.InsertWindowsUpdatesFuncInvoked)
}
func TestIngestKubequeryInfo(t *testing.T) {
err := ingestKubequeryInfo(t.Context(), slog.New(slog.DiscardHandler), &fleet.Host{}, nil)
require.Error(t, err)