mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 00:49:03 +00:00
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
This commit is contained in:
parent
ccd6746633
commit
5c868c9d3d
13 changed files with 168 additions and 55 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
q.DiscardData,
|
||||
q.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "updating query")
|
||||
}
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "rows affected updating query")
|
||||
}
|
||||
if rows == 0 {
|
||||
return ctxerr.Wrap(ctx, notFound("Query").WithID(q.ID))
|
||||
return ctxerr.Wrap(ctx, err, "exec SaveQuery update")
|
||||
}
|
||||
|
||||
return nil
|
||||
if resultsStmt != nil {
|
||||
_, err := resultsStmt.ExecContext(
|
||||
ctx,
|
||||
q.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "exec SaveQuery delete query results")
|
||||
}
|
||||
}
|
||||
|
||||
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, '<deleted>') AS author_name,
|
||||
COALESCE(u.email, '') AS author_email,
|
||||
JSON_EXTRACT(json_value, '$.user_time_p50') as user_time_p50,
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue