From 5c868c9d3da226a3dfc4e982ad6715d156dac314 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Mon, 9 Oct 2023 17:43:17 -0400 Subject: [PATCH] feat: deletion for query results (#14302) # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests --- cmd/fleetctl/apply_test.go | 9 +- server/datastore/mysql/packs_test.go | 2 +- server/datastore/mysql/queries.go | 105 ++++++++++++++---- server/datastore/mysql/queries_test.go | 26 +++-- .../datastore/mysql/scheduled_queries_test.go | 8 +- server/fleet/datastore.go | 4 +- server/fleet/queries.go | 2 + server/mock/datastore_mock.go | 12 +- server/service/global_schedule_test.go | 2 +- server/service/integration_core_test.go | 4 + server/service/queries.go | 38 ++++++- server/service/queries_test.go | 7 +- server/service/team_schedule_test.go | 2 +- 13 files changed, 167 insertions(+), 54 deletions(-) diff --git a/cmd/fleetctl/apply_test.go b/cmd/fleetctl/apply_test.go index 395fe0a6cdc2..137c409ec786 100644 --- a/cmd/fleetctl/apply_test.go +++ b/cmd/fleetctl/apply_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "crypto/sha256" - "database/sql" "encoding/json" "errors" "fmt" @@ -1204,9 +1203,9 @@ spec: // Apply queries. var appliedQueries []*fleet.Query ds.QueryByNameFunc = func(ctx context.Context, teamID *uint, name string, opts ...fleet.OptionalArg) (*fleet.Query, error) { - return nil, sql.ErrNoRows + return nil, ¬FoundError{} } - ds.ApplyQueriesFunc = func(ctx context.Context, authorID uint, queries []*fleet.Query) error { + ds.ApplyQueriesFunc = func(ctx context.Context, authorID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]bool) error { appliedQueries = queries return nil } @@ -1305,9 +1304,9 @@ func TestApplyQueries(t *testing.T) { var appliedQueries []*fleet.Query ds.QueryByNameFunc = func(ctx context.Context, teamID *uint, name string, opts ...fleet.OptionalArg) (*fleet.Query, error) { - return nil, sql.ErrNoRows + return nil, ¬FoundError{} } - ds.ApplyQueriesFunc = func(ctx context.Context, authorID uint, queries []*fleet.Query) error { + ds.ApplyQueriesFunc = func(ctx context.Context, authorID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]bool) error { appliedQueries = queries return nil } diff --git a/server/datastore/mysql/packs_test.go b/server/datastore/mysql/packs_test.go index 09a28361c525..21eff1c89181 100644 --- a/server/datastore/mysql/packs_test.go +++ b/server/datastore/mysql/packs_test.go @@ -145,7 +145,7 @@ func setupPackSpecsTest(t *testing.T, ds fleet.Datastore) []*fleet.PackSpec { {Name: "bar", Description: "do some bars", Query: "select baz from bar"}, } // Zach creates some queries - err := ds.ApplyQueries(context.Background(), zwass.ID, queries) + err := ds.ApplyQueries(context.Background(), zwass.ID, queries, nil) require.Nil(t, err) labels := []*fleet.LabelSpec{ diff --git a/server/datastore/mysql/queries.go b/server/datastore/mysql/queries.go index 1ea22ef2bfd3..60f5e87210f1 100644 --- a/server/datastore/mysql/queries.go +++ b/server/datastore/mysql/queries.go @@ -10,7 +10,7 @@ import ( "github.com/jmoiron/sqlx" ) -func (ds *Datastore) ApplyQueries(ctx context.Context, authorID uint, queries []*fleet.Query) (err error) { +func (ds *Datastore) ApplyQueries(ctx context.Context, authorID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]bool) (err error) { tx, err := ds.writer(ctx).BeginTxx(ctx, nil) if err != nil { return ctxerr.Wrap(ctx, err, "begin ApplyQueries transaction") @@ -29,7 +29,7 @@ func (ds *Datastore) ApplyQueries(ctx context.Context, authorID uint, queries [] } }() - sql := ` + insertSql := ` INSERT INTO queries ( name, description, @@ -60,12 +60,23 @@ func (ds *Datastore) ApplyQueries(ctx context.Context, authorID uint, queries [] automations_enabled = VALUES(automations_enabled), logging_type = VALUES(logging_type) ` - stmt, err := tx.PrepareContext(ctx, sql) + stmt, err := tx.PrepareContext(ctx, insertSql) if err != nil { return ctxerr.Wrap(ctx, err, "prepare ApplyQueries insert") } defer stmt.Close() + var resultsStmt *sql.Stmt + if len(queriesToDiscardResults) > 0 { + resultsSql := `DELETE FROM query_results WHERE query_id = ?` + resultsStmt, err = tx.PrepareContext(ctx, resultsSql) + if err != nil { + return ctxerr.Wrap(ctx, err, "prepare ApplyQueries delete query results") + } + defer resultsStmt.Close() + + } + for _, q := range queries { if err := q.Verify(); err != nil { return ctxerr.Wrap(ctx, err) @@ -90,6 +101,16 @@ func (ds *Datastore) ApplyQueries(ctx context.Context, authorID uint, queries [] } } + for id := range queriesToDiscardResults { + _, err := resultsStmt.ExecContext( + ctx, + id, + ) + if err != nil { + return ctxerr.Wrap(ctx, err, "exec ApplyQueries delete query results") + } + } + err = tx.Commit() return ctxerr.Wrap(ctx, err, "commit ApplyQueries transaction") } @@ -164,8 +185,9 @@ func (ds *Datastore) NewQuery( min_osquery_version, schedule_interval, automations_enabled, - logging_type - ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) + logging_type, + discard_data + ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ` result, err := ds.writer(ctx).ExecContext( ctx, @@ -183,6 +205,7 @@ func (ds *Datastore) NewQuery( query.Interval, query.AutomationsEnabled, query.Logging, + query.DiscardData, ) if err != nil && isDuplicate(err) { @@ -198,8 +221,26 @@ func (ds *Datastore) NewQuery( } // SaveQuery saves changes to a Query. -func (ds *Datastore) SaveQuery(ctx context.Context, q *fleet.Query) error { - sql := ` +func (ds *Datastore) SaveQuery(ctx context.Context, q *fleet.Query, shouldDiscardResults bool) (err error) { + tx, err := ds.writer(ctx).BeginTxx(ctx, nil) + if err != nil { + return ctxerr.Wrap(ctx, err, "begin SaveQuery transaction") + } + + defer func() { + if err != nil { + rbErr := tx.Rollback() + // It seems possible that there might be a case in + // which the error we are dealing with here was thrown + // by the call to tx.Commit(), and the docs suggest + // this call would then result in sql.ErrTxDone. + if rbErr != nil && rbErr != sql.ErrTxDone { + panic(fmt.Sprintf("got err '%s' rolling back after err '%s'", rbErr, err)) + } + } + }() + + updateSql := ` UPDATE queries SET name = ?, description = ?, @@ -213,12 +254,30 @@ func (ds *Datastore) SaveQuery(ctx context.Context, q *fleet.Query) error { min_osquery_version = ?, schedule_interval = ?, automations_enabled = ?, - logging_type = ? + logging_type = ?, + discard_data = ? WHERE id = ? ` - result, err := ds.writer(ctx).ExecContext( + + stmt, err := tx.PrepareContext(ctx, updateSql) + if err != nil { + return ctxerr.Wrap(ctx, err, "prepare SaveQuery update") + } + defer stmt.Close() + + var resultsStmt *sql.Stmt + if shouldDiscardResults { + resultsSql := `DELETE FROM query_results WHERE query_id = ?` + resultsStmt, err = tx.PrepareContext(ctx, resultsSql) + if err != nil { + return ctxerr.Wrap(ctx, err, "prepare SaveQuery delete query results") + } + defer resultsStmt.Close() + + } + + _, err = stmt.ExecContext( ctx, - sql, q.Name, q.Description, q.Query, @@ -232,19 +291,25 @@ func (ds *Datastore) SaveQuery(ctx context.Context, q *fleet.Query) error { q.Interval, q.AutomationsEnabled, q.Logging, - q.ID) - if err != nil { - return ctxerr.Wrap(ctx, err, "updating query") - } - rows, err := result.RowsAffected() + q.DiscardData, + q.ID, + ) if err != nil { - return ctxerr.Wrap(ctx, err, "rows affected updating query") + return ctxerr.Wrap(ctx, err, "exec SaveQuery update") } - if rows == 0 { - return ctxerr.Wrap(ctx, notFound("Query").WithID(q.ID)) + + if resultsStmt != nil { + _, err := resultsStmt.ExecContext( + ctx, + q.ID, + ) + if err != nil { + return ctxerr.Wrap(ctx, err, "exec SaveQuery delete query results") + } } - return nil + err = tx.Commit() + return ctxerr.Wrap(ctx, err, "commit SaveQuery transaction") } func (ds *Datastore) DeleteQuery( @@ -301,6 +366,7 @@ func (ds *Datastore) Query(ctx context.Context, id uint) (*fleet.Query, error) { q.logging_type, q.created_at, q.updated_at, + q.discard_data, COALESCE(NULLIF(u.name, ''), u.email, '') AS author_name, COALESCE(u.email, '') AS author_email, JSON_EXTRACT(json_value, '$.user_time_p50') as user_time_p50, @@ -350,6 +416,7 @@ func (ds *Datastore) ListQueries(ctx context.Context, opt fleet.ListQueryOptions q.logging_type, q.created_at, q.updated_at, + q.discard_data, COALESCE(u.name, '') AS author_name, COALESCE(u.email, '') AS author_email, JSON_EXTRACT(json_value, '$.user_time_p50') as user_time_p50, diff --git a/server/datastore/mysql/queries_test.go b/server/datastore/mysql/queries_test.go index 29bd76c113d2..31a3d0a54203 100644 --- a/server/datastore/mysql/queries_test.go +++ b/server/datastore/mysql/queries_test.go @@ -59,16 +59,18 @@ func testQueriesApply(t *testing.T, ds *Datastore) { MinOsqueryVersion: "5.2.1", AutomationsEnabled: true, Logging: "differential", + DiscardData: true, }, { Name: "bar", Description: "do some bars", Query: "select baz from bar", + DiscardData: true, }, } // Zach creates some queries - err := ds.ApplyQueries(context.Background(), zwass.ID, expectedQueries) + err := ds.ApplyQueries(context.Background(), zwass.ID, expectedQueries, nil) require.NoError(t, err) queries, err := ds.ListQueries(context.Background(), fleet.ListQueryOptions{}) @@ -88,7 +90,7 @@ func testQueriesApply(t *testing.T, ds *Datastore) { // Victor modifies a query (but also pushes the same version of the // first query) expectedQueries[1].Query = "not really a valid query ;)" - err = ds.ApplyQueries(context.Background(), groob.ID, expectedQueries) + err = ds.ApplyQueries(context.Background(), groob.ID, expectedQueries, nil) require.NoError(t, err) queries, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{}) @@ -111,9 +113,10 @@ func testQueriesApply(t *testing.T, ds *Datastore) { Name: "trouble", Description: "Look out!", Query: "select * from time", + DiscardData: true, }, ) - err = ds.ApplyQueries(context.Background(), zwass.ID, []*fleet.Query{expectedQueries[2]}) + err = ds.ApplyQueries(context.Background(), zwass.ID, []*fleet.Query{expectedQueries[2]}, nil) require.NoError(t, err) queries, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{}) @@ -150,7 +153,7 @@ func testQueriesApply(t *testing.T, ds *Datastore) { Logging: "differential", }, } - err = ds.ApplyQueries(context.Background(), zwass.ID, invalidQueries) + err = ds.ApplyQueries(context.Background(), zwass.ID, invalidQueries, nil) require.ErrorIs(t, err, fleet.ErrQueryInvalidPlatform) } @@ -276,8 +279,9 @@ func testQueriesSave(t *testing.T, ds *Datastore) { query.MinOsqueryVersion = "5.2.1" query.AutomationsEnabled = true query.Logging = "differential" + query.DiscardData = true - err = ds.SaveQuery(context.Background(), query) + err = ds.SaveQuery(context.Background(), query, true) require.NoError(t, err) actual, err := ds.Query(context.Background(), query.ID) @@ -296,10 +300,11 @@ func testQueriesList(t *testing.T, ds *Datastore) { for i := 0; i < 10; i++ { _, err := ds.NewQuery(context.Background(), &fleet.Query{ - Name: fmt.Sprintf("name%02d", i), - Query: fmt.Sprintf("query%02d", i), - Saved: true, - AuthorID: &user.ID, + Name: fmt.Sprintf("name%02d", i), + Query: fmt.Sprintf("query%02d", i), + Saved: true, + AuthorID: &user.ID, + DiscardData: true, }) require.Nil(t, err) } @@ -319,6 +324,7 @@ func testQueriesList(t *testing.T, ds *Datastore) { require.Equal(t, 10, len(results)) require.Equal(t, "Zach", results[0].AuthorName) require.Equal(t, "zwass@fleet.co", results[0].AuthorEmail) + require.True(t, results[0].DiscardData) idWithAgg := results[0].ID @@ -351,7 +357,7 @@ func testQueriesLoadPacksForQueries(t *testing.T, ds *Datastore) { {Name: "q1", Query: "select * from time"}, {Name: "q2", Query: "select * from osquery_info"}, } - err := ds.ApplyQueries(context.Background(), zwass.ID, queries) + err := ds.ApplyQueries(context.Background(), zwass.ID, queries, nil) require.NoError(t, err) specs := []*fleet.PackSpec{ diff --git a/server/datastore/mysql/scheduled_queries_test.go b/server/datastore/mysql/scheduled_queries_test.go index 9231587f99f2..04568fea6037 100644 --- a/server/datastore/mysql/scheduled_queries_test.go +++ b/server/datastore/mysql/scheduled_queries_test.go @@ -44,7 +44,7 @@ func testScheduledQueriesListInPackWithStats(t *testing.T, ds *Datastore) { {Name: "foo", Description: "get the foos", Query: "select * from foo"}, {Name: "bar", Description: "do some bars", Query: "select baz from bar"}, } - err := ds.ApplyQueries(context.Background(), zwass.ID, queries) + err := ds.ApplyQueries(context.Background(), zwass.ID, queries, nil) require.NoError(t, err) specs := []*fleet.PackSpec{ @@ -134,7 +134,7 @@ func testScheduledQueriesListInPack(t *testing.T, ds *Datastore) { {Name: "foo", Description: "get the foos", Query: "select * from foo"}, {Name: "bar", Description: "do some bars", Query: "select baz from bar"}, } - err := ds.ApplyQueries(context.Background(), zwass.ID, queries) + err := ds.ApplyQueries(context.Background(), zwass.ID, queries, nil) require.NoError(t, err) specs := []*fleet.PackSpec{ @@ -327,7 +327,7 @@ func testScheduledQueriesCascadingDelete(t *testing.T, ds *Datastore) { {Name: "foo", Description: "get the foos", Query: "select * from foo"}, {Name: "bar", Description: "do some bars", Query: "select baz from bar"}, } - err := ds.ApplyQueries(context.Background(), zwass.ID, queries) + err := ds.ApplyQueries(context.Background(), zwass.ID, queries, nil) require.Nil(t, err) specs := []*fleet.PackSpec{ @@ -379,7 +379,7 @@ func testScheduledQueriesIDsByName(t *testing.T, ds *Datastore) { {Name: "foo2", Description: "get the foos", Query: "select * from foo2"}, {Name: "bar2", Description: "do some bars", Query: "select * from bar2"}, } - err := ds.ApplyQueries(ctx, user.ID, queries) + err := ds.ApplyQueries(ctx, user.ID, queries, nil) require.NoError(t, err) specs := []*fleet.PackSpec{ diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index 13db70dc96c0..0e4f2eacefd5 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -66,11 +66,11 @@ type Datastore interface { // ApplyQueries applies a list of queries (likely from a yaml file) to the datastore. Existing queries are updated, // and new queries are created. - ApplyQueries(ctx context.Context, authorID uint, queries []*Query) error + ApplyQueries(ctx context.Context, authorID uint, queries []*Query, queriesToDiscardResults map[uint]bool) error // NewQuery creates a new query object in thie datastore. The returned query should have the ID updated. NewQuery(ctx context.Context, query *Query, opts ...OptionalArg) (*Query, error) // SaveQuery saves changes to an existing query object. - SaveQuery(ctx context.Context, query *Query) error + SaveQuery(ctx context.Context, query *Query, shouldDiscardResults bool) error // DeleteQuery deletes an existing query object on a team. If teamID is nil, then the query is // looked up in the 'global' team. DeleteQuery(ctx context.Context, teamID *uint, name string) error diff --git a/server/fleet/queries.go b/server/fleet/queries.go index ca3bb8876bb3..98ac35192267 100644 --- a/server/fleet/queries.go +++ b/server/fleet/queries.go @@ -36,6 +36,8 @@ type QueryPayload struct { AutomationsEnabled *bool `json:"automations_enabled"` // Logging is set to "snapshot" if not set when creating a query. Logging *string `json:"logging"` + // DiscardData is set to false if not set when creating a query. + DiscardData *bool `json:"discard_data"` } // Query represents a osquery query to run on devices. diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index 1b049e60200d..7d66437592ee 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -56,11 +56,11 @@ type PendingEmailChangeFunc func(ctx context.Context, userID uint, newEmail stri type ConfirmPendingEmailChangeFunc func(ctx context.Context, userID uint, token string) (string, error) -type ApplyQueriesFunc func(ctx context.Context, authorID uint, queries []*fleet.Query) error +type ApplyQueriesFunc func(ctx context.Context, authorID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]bool) error type NewQueryFunc func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) -type SaveQueryFunc func(ctx context.Context, query *fleet.Query) error +type SaveQueryFunc func(ctx context.Context, query *fleet.Query, shouldDiscardResults bool) error type DeleteQueryFunc func(ctx context.Context, teamID *uint, name string) error @@ -1815,11 +1815,11 @@ func (s *DataStore) ConfirmPendingEmailChange(ctx context.Context, userID uint, return s.ConfirmPendingEmailChangeFunc(ctx, userID, token) } -func (s *DataStore) ApplyQueries(ctx context.Context, authorID uint, queries []*fleet.Query) error { +func (s *DataStore) ApplyQueries(ctx context.Context, authorID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]bool) error { s.mu.Lock() s.ApplyQueriesFuncInvoked = true s.mu.Unlock() - return s.ApplyQueriesFunc(ctx, authorID, queries) + return s.ApplyQueriesFunc(ctx, authorID, queries, queriesToDiscardResults) } func (s *DataStore) NewQuery(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) { @@ -1829,11 +1829,11 @@ func (s *DataStore) NewQuery(ctx context.Context, query *fleet.Query, opts ...fl return s.NewQueryFunc(ctx, query, opts...) } -func (s *DataStore) SaveQuery(ctx context.Context, query *fleet.Query) error { +func (s *DataStore) SaveQuery(ctx context.Context, query *fleet.Query, shouldDiscardResults bool) error { s.mu.Lock() s.SaveQueryFuncInvoked = true s.mu.Unlock() - return s.SaveQueryFunc(ctx, query) + return s.SaveQueryFunc(ctx, query, shouldDiscardResults) } func (s *DataStore) DeleteQuery(ctx context.Context, teamID *uint, name string) error { diff --git a/server/service/global_schedule_test.go b/server/service/global_schedule_test.go index 25f25608c624..d29e0f3f408b 100644 --- a/server/service/global_schedule_test.go +++ b/server/service/global_schedule_test.go @@ -24,7 +24,7 @@ func TestGlobalScheduleAuth(t *testing.T) { Query: "SELECT 1;", }, nil } - ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query) error { + ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query, shouldDiscardResults bool) error { return nil } ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error { diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index d3ab73919f39..89650857d812 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -2548,6 +2548,10 @@ func (s *integrationTestSuite) TestScheduledQueries() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", query.ID), fleet.QueryPayload{Description: ptr.String("updated")}, http.StatusOK, &modQryResp) assert.Equal(t, "updated", modQryResp.Query.Description) + // TODO(jahziel): check that the query results were deleted + + // TODO(jahziel): check that the query results were deleted after setting `discard_data` + // modify a non-existing query s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", query.ID+1), fleet.QueryPayload{Description: ptr.String("updated")}, http.StatusNotFound, &modQryResp) diff --git a/server/service/queries.go b/server/service/queries.go index a911790e90d7..7a59033790c5 100644 --- a/server/service/queries.go +++ b/server/service/queries.go @@ -250,6 +250,7 @@ func modifyQueryEndpoint(ctx context.Context, request interface{}, svc fleet.Ser func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPayload) (*fleet.Query, error) { // Load query first to determine if the user can modify it. query, err := svc.ds.Query(ctx, id) + shouldDiscardQueryResults := false if err != nil { setAuthCheckedOnPreAuthErr(ctx) return nil, err @@ -271,6 +272,9 @@ func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPaylo query.Description = *p.Description } if p.Query != nil { + if query.Query != *p.Query { + shouldDiscardQueryResults = true + } query.Query = *p.Query } if p.Interval != nil { @@ -286,15 +290,24 @@ func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPaylo query.AutomationsEnabled = *p.AutomationsEnabled } if p.Logging != nil { + if query.Logging != *p.Logging && *p.Logging != fleet.LoggingSnapshot { + shouldDiscardQueryResults = true + } query.Logging = *p.Logging } if p.ObserverCanRun != nil { query.ObserverCanRun = *p.ObserverCanRun } + if p.DiscardData != nil { + if *p.DiscardData && *p.DiscardData != query.DiscardData { + shouldDiscardQueryResults = true + } + query.DiscardData = *p.DiscardData + } logging.WithExtras(ctx, "name", query.Name, "sql", query.Query) - if err := svc.ds.SaveQuery(ctx, query); err != nil { + if err := svc.ds.SaveQuery(ctx, query, shouldDiscardQueryResults); err != nil { return nil, err } @@ -518,11 +531,32 @@ func (svc *Service) ApplyQuerySpecs(ctx context.Context, specs []*fleet.QuerySpe } } // 3. Apply the queries. + + // first, find out if we should delete query results + queriesToDiscardResults := make(map[uint]bool) + for _, query := range queries { + dbQuery, err := svc.ds.QueryByName(ctx, query.TeamID, query.Name) + if err != nil && !fleet.IsNotFound(err) { + return ctxerr.Wrap(ctx, err, "fetching saved query") + } + + if dbQuery == nil { + // then we're creating a new query, so move on. + continue + } + + if (query.DiscardData && query.DiscardData != dbQuery.DiscardData) || + (query.Logging != dbQuery.Logging && query.Logging != fleet.LoggingSnapshot) || + query.Query != dbQuery.Query { + queriesToDiscardResults[dbQuery.ID] = true + } + } + vc, ok := viewer.FromContext(ctx) if !ok { return ctxerr.New(ctx, "user must be authenticated to apply queries") } - err := svc.ds.ApplyQueries(ctx, vc.UserID(), queries) + err := svc.ds.ApplyQueries(ctx, vc.UserID(), queries, queriesToDiscardResults) if err != nil { return ctxerr.Wrap(ctx, err, "applying queries") } diff --git a/server/service/queries_test.go b/server/service/queries_test.go index 95b4ab764c76..02e1d6c447b1 100644 --- a/server/service/queries_test.go +++ b/server/service/queries_test.go @@ -239,7 +239,7 @@ func TestQueryPayloadValidationModify(t *testing.T) { ObserverCanRun: false, }, nil } - ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query) error { + ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query, shouldDiscardResults bool) error { assert.NotEmpty(t, query) return nil } @@ -250,6 +250,7 @@ func TestQueryPayloadValidationModify(t *testing.T) { assert.NotEmpty(t, act.Name) return nil } + svc, ctx := newTestService(t, ds, nil, nil) testCases := []struct { @@ -446,7 +447,7 @@ func TestQueryAuth(t *testing.T) { } return nil, newNotFoundError() } - ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query) error { + ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query, shouldDiscardResults bool) error { return nil } ds.DeleteQueryFunc = func(ctx context.Context, teamID *uint, name string) error { @@ -458,7 +459,7 @@ func TestQueryAuth(t *testing.T) { ds.ListQueriesFunc = func(ctx context.Context, opts fleet.ListQueryOptions) ([]*fleet.Query, error) { return nil, nil } - ds.ApplyQueriesFunc = func(ctx context.Context, authID uint, queries []*fleet.Query) error { + ds.ApplyQueriesFunc = func(ctx context.Context, authID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]bool) error { return nil } diff --git a/server/service/team_schedule_test.go b/server/service/team_schedule_test.go index ab966e5be6f9..41fa628ae4e2 100644 --- a/server/service/team_schedule_test.go +++ b/server/service/team_schedule_test.go @@ -33,7 +33,7 @@ func TestTeamScheduleAuth(t *testing.T) { TeamID: nil, }, nil } - ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query) error { + ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query, shouldDiscardResults bool) error { return nil } ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {