diff --git a/orbit/changes/25987-installed-win-updates-only b/orbit/changes/25987-installed-win-updates-only new file mode 100644 index 0000000000..0a6dbe8d06 --- /dev/null +++ b/orbit/changes/25987-installed-win-updates-only @@ -0,0 +1 @@ +- Updated the 'windows_updates' Orbit table so that results are only returned iff there are non-installed windows updates. \ No newline at end of file diff --git a/orbit/pkg/table/windowsupdatetable/windowsupdate.go b/orbit/pkg/table/windowsupdatetable/windows_update.go similarity index 87% rename from orbit/pkg/table/windowsupdatetable/windowsupdate.go rename to orbit/pkg/table/windowsupdatetable/windows_update.go index 82acadbed7..7dd4c28049 100644 --- a/orbit/pkg/table/windowsupdatetable/windowsupdate.go +++ b/orbit/pkg/table/windowsupdatetable/windows_update.go @@ -27,6 +27,13 @@ const ( HistoryTable ) +type windowsUpdatesSearcher interface { + QueryHistoryAll() ([]*windowsupdate.IUpdateHistoryEntry, error) + Search(criteria string) (*windowsupdate.ISearchResult, error) +} + +type queryFuncType func(searcher windowsUpdatesSearcher) (interface{}, error) + type Table struct { logger zerolog.Logger queryFunc queryFuncType @@ -55,16 +62,23 @@ func TablePlugin(mode tableMode, logger zerolog.Logger) *table.Plugin { return table.NewPlugin(t.name, columns, t.generate) } -func queryUpdates(searcher *windowsupdate.IUpdateSearcher) (interface{}, error) { - return searcher.Search("Type='Software'") +func queryUpdates(searcher windowsUpdatesSearcher) (interface{}, error) { + searchResult, err := searcher.Search("Type='Software' AND IsInstalled=0") + if err != nil { + return nil, err + } + + // We only care about the results iff we got some updates + if searchResult != nil && len(searchResult.Updates) == 0 { + return nil, nil + } + return searchResult, nil } -func queryHistory(searcher *windowsupdate.IUpdateSearcher) (interface{}, error) { +func queryHistory(searcher windowsUpdatesSearcher) (interface{}, error) { return searcher.QueryHistoryAll() } -type queryFuncType func(*windowsupdate.IUpdateSearcher) (interface{}, error) - func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { var results []map[string]string diff --git a/orbit/pkg/table/windowsupdatetable/windows_update_test.go b/orbit/pkg/table/windowsupdatetable/windows_update_test.go new file mode 100644 index 0000000000..88139ab6f0 --- /dev/null +++ b/orbit/pkg/table/windowsupdatetable/windows_update_test.go @@ -0,0 +1,97 @@ +//go:build windows +// +build windows + +// based on github.com/kolide/launcher/pkg/osquery/tables +package windowsupdatetable + +import ( + "context" + "github.com/fleetdm/fleet/v4/orbit/pkg/windows/windowsupdate" + "strings" + "testing" + "time" + + "github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" +) + +type windowsUpdatesSearcherMock struct { + searchCriteria string + SearchResult *windowsupdate.ISearchResult + History []*windowsupdate.IUpdateHistoryEntry +} + +func (wus *windowsUpdatesSearcherMock) Search(criteria string) (*windowsupdate.ISearchResult, error) { + wus.searchCriteria = criteria + return wus.SearchResult, nil +} + +func (wus *windowsUpdatesSearcherMock) QueryHistoryAll() ([]*windowsupdate.IUpdateHistoryEntry, error) { + return wus.History, nil +} + +func TestQueryUpdates(t *testing.T) { + t.Run("the right criteria is used", func(t *testing.T) { + searcher := &windowsUpdatesSearcherMock{} + _, err := queryUpdates(searcher) + require.NoError(t, err) + + criteriaParts := strings.Split(searcher.searchCriteria, " AND ") + require.Contains(t, criteriaParts, "Type='Software'") + require.Contains(t, criteriaParts, "IsInstalled=0") + }) + + t.Run("only return results iff any updates", func(t *testing.T) { + testCases := []struct { + updates []*windowsupdate.IUpdate + isNil bool + }{ + {updates: nil, isNil: true}, + {updates: []*windowsupdate.IUpdate{}, isNil: true}, + {updates: []*windowsupdate.IUpdate{{}}, isNil: false}, + } + + for _, tt := range testCases { + searcher := &windowsUpdatesSearcherMock{ + SearchResult: &windowsupdate.ISearchResult{ + Updates: tt.updates, + }, + } + r, err := queryUpdates(searcher) + require.NoError(t, err) + require.Equal(t, r == nil, tt.isNil) + } + }) +} + +func TestTable(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + queryFunc queryFuncType + }{ + {name: "updates", queryFunc: queryUpdates}, + {name: "history", queryFunc: queryHistory}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + table := Table{ + logger: zerolog.Nop(), + queryFunc: tt.queryFunc, + } + + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + + // ci doesn't return data, but we can, at least, check that the underlying API doesn't error. + _, err := table.generate(ctx, tablehelpers.MockQueryContext(nil)) + require.NoError(t, err, "generate") + }) + } +} diff --git a/orbit/pkg/table/windowsupdatetable/windowsupdate_test.go b/orbit/pkg/table/windowsupdatetable/windowsupdate_test.go deleted file mode 100644 index f4a74bb14f..0000000000 --- a/orbit/pkg/table/windowsupdatetable/windowsupdate_test.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build windows -// +build windows - -// based on github.com/kolide/launcher/pkg/osquery/tables -package windowsupdatetable - -import ( - "context" - "testing" - "time" - - "github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" -) - -func TestTable(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - queryFunc queryFuncType - }{ - {name: "updates", queryFunc: queryUpdates}, - {name: "history", queryFunc: queryHistory}, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - table := Table{ - logger: zerolog.Nop(), - queryFunc: tt.queryFunc, - } - - ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) - defer cancel() - - // ci doesn't return data, but we can, at least, check that the underlying API doesn't error. - _, err := table.generate(ctx, tablehelpers.MockQueryContext(nil)) - require.NoError(t, err, "generate") - }) - } -}