Return non-installed 'windows_updates' only. (#28531)

Fixes #25987 

When querying the 'windows_updates' Orbit table return non-installed windows updates only.
This commit is contained in:
Juan Fernandez
2025-04-28 18:23:13 -04:00
committed by GitHub
parent eac6ccb4e4
commit ff578a9e9f
4 changed files with 117 additions and 51 deletions
@@ -0,0 +1 @@
- Updated the 'windows_updates' Orbit table so that results are only returned iff there are non-installed windows updates.
@@ -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
@@ -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")
})
}
}
@@ -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")
})
}
}