2016-11-16 13:47:49 +00:00
|
|
|
package mysql
|
|
|
|
|
|
|
|
import (
|
2017-01-13 18:35:25 +00:00
|
|
|
"database/sql"
|
2018-01-03 19:18:05 +00:00
|
|
|
"fmt"
|
2017-01-13 18:35:25 +00:00
|
|
|
|
2016-12-06 18:22:28 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2017-06-22 19:50:45 +00:00
|
|
|
"github.com/kolide/fleet/server/kolide"
|
2017-01-13 18:35:25 +00:00
|
|
|
"github.com/pkg/errors"
|
2016-11-16 13:47:49 +00:00
|
|
|
)
|
|
|
|
|
2018-01-03 19:18:05 +00:00
|
|
|
func (d *Datastore) ApplyQueries(authorID uint, queries []*kolide.Query) (err error) {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "begin ApplyQueries 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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
sql := `
|
|
|
|
INSERT INTO queries (
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
query,
|
|
|
|
author_id,
|
|
|
|
saved,
|
|
|
|
deleted
|
|
|
|
) VALUES ( ?, ?, ?, ?, true, false )
|
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
|
name = VALUES(name),
|
|
|
|
description = VALUES(description),
|
|
|
|
query = VALUES(query),
|
|
|
|
author_id = VALUES(author_id),
|
|
|
|
saved = VALUES(saved),
|
|
|
|
deleted = VALUES(deleted)
|
|
|
|
`
|
|
|
|
stmt, err := tx.Prepare(sql)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "prepare ApplyQueries insert")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, q := range queries {
|
|
|
|
_, err := stmt.Exec(q.Name, q.Description, q.Query, authorID)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "exec ApplyQueries insert")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
return errors.Wrap(err, "commit ApplyQueries transaction")
|
|
|
|
}
|
|
|
|
|
2017-05-30 19:42:00 +00:00
|
|
|
func (d *Datastore) QueryByName(name string, opts ...kolide.OptionalArg) (*kolide.Query, bool, error) {
|
|
|
|
db := d.getTransaction(opts)
|
2017-01-13 18:35:25 +00:00
|
|
|
sqlStatement := `
|
|
|
|
SELECT *
|
|
|
|
FROM queries
|
|
|
|
WHERE name = ? AND NOT deleted
|
|
|
|
`
|
|
|
|
var query kolide.Query
|
2017-05-30 19:42:00 +00:00
|
|
|
err := db.Get(&query, sqlStatement, name)
|
2017-01-13 18:35:25 +00:00
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return nil, false, nil
|
|
|
|
}
|
|
|
|
return nil, false, errors.Wrap(err, "selecting query by name")
|
|
|
|
}
|
2018-01-03 19:18:05 +00:00
|
|
|
|
|
|
|
if err := d.loadPacksForQueries([]*kolide.Query{&query}); err != nil {
|
|
|
|
return nil, false, errors.Wrap(err, "loading packs for query")
|
|
|
|
}
|
|
|
|
|
2017-01-13 18:35:25 +00:00
|
|
|
return &query, true, nil
|
|
|
|
}
|
|
|
|
|
2017-02-02 20:18:44 +00:00
|
|
|
// NewQuery creates a New Query. If a query with the same name was soft-deleted,
|
|
|
|
// NewQuery will replace the old one.
|
2017-05-30 19:42:00 +00:00
|
|
|
func (d *Datastore) NewQuery(query *kolide.Query, opts ...kolide.OptionalArg) (*kolide.Query, error) {
|
|
|
|
db := d.getTransaction(opts)
|
2017-02-02 20:18:44 +00:00
|
|
|
var (
|
|
|
|
deletedQuery kolide.Query
|
|
|
|
sqlStatement string
|
|
|
|
)
|
2017-05-30 19:42:00 +00:00
|
|
|
err := db.Get(&deletedQuery,
|
2017-02-02 20:18:44 +00:00
|
|
|
"SELECT * FROM queries WHERE name = ? AND deleted", query.Name)
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
sqlStatement = `
|
|
|
|
REPLACE INTO queries (
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
query,
|
|
|
|
saved,
|
|
|
|
author_id,
|
|
|
|
deleted
|
|
|
|
) VALUES ( ?, ?, ?, ?, ?, ? )
|
|
|
|
`
|
|
|
|
case sql.ErrNoRows:
|
|
|
|
sqlStatement = `
|
|
|
|
INSERT INTO queries (
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
query,
|
|
|
|
saved,
|
|
|
|
author_id,
|
|
|
|
deleted
|
|
|
|
) VALUES ( ?, ?, ?, ?, ?, ? )
|
|
|
|
`
|
|
|
|
default:
|
|
|
|
return nil, errors.Wrap(err, "check for existing Query")
|
2017-01-16 22:20:15 +00:00
|
|
|
}
|
2017-02-02 20:18:44 +00:00
|
|
|
deleted := false
|
2017-05-30 19:42:00 +00:00
|
|
|
result, err := db.Exec(sqlStatement, query.Name, query.Description, query.Query, query.Saved, query.AuthorID, deleted)
|
2017-02-02 20:18:44 +00:00
|
|
|
if err != nil && isDuplicate(err) {
|
|
|
|
return nil, alreadyExists("Query", deletedQuery.ID)
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creating new Query")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
id, _ := result.LastInsertId()
|
|
|
|
query.ID = uint(id)
|
2017-01-06 01:48:56 +00:00
|
|
|
query.Packs = []kolide.Pack{}
|
2016-11-16 13:47:49 +00:00
|
|
|
return query, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveQuery saves changes to a Query.
|
|
|
|
func (d *Datastore) SaveQuery(q *kolide.Query) error {
|
|
|
|
sql := `
|
|
|
|
UPDATE queries
|
2016-12-13 22:22:05 +00:00
|
|
|
SET name = ?, description = ?, query = ?, author_id = ?, saved = ?
|
2016-11-16 13:47:49 +00:00
|
|
|
WHERE id = ? AND NOT deleted
|
|
|
|
`
|
2017-03-30 22:03:48 +00:00
|
|
|
result, err := d.db.Exec(sql, q.Name, q.Description, q.Query, q.AuthorID, q.Saved, q.ID)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return errors.Wrap(err, "updating query")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2017-03-30 22:03:48 +00:00
|
|
|
rows, err := result.RowsAffected()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "rows affected updating query")
|
|
|
|
}
|
|
|
|
if rows == 0 {
|
|
|
|
return notFound("Query").WithID(q.ID)
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteQuery soft deletes Query identified by Query.ID
|
2018-05-04 18:05:55 +00:00
|
|
|
func (d *Datastore) DeleteQuery(name string) error {
|
|
|
|
return d.deleteEntityByName("queries", name)
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2016-12-09 17:12:45 +00:00
|
|
|
// DeleteQueries (soft) deletes the existing query objects with the provided
|
|
|
|
// IDs. The number of deleted queries is returned along with any error.
|
|
|
|
func (d *Datastore) DeleteQueries(ids []uint) (uint, error) {
|
|
|
|
sql := `
|
|
|
|
UPDATE queries
|
|
|
|
SET deleted_at = NOW(), deleted = true
|
2017-03-30 22:03:48 +00:00
|
|
|
WHERE id IN (?) AND NOT deleted
|
2016-12-09 17:12:45 +00:00
|
|
|
`
|
|
|
|
query, args, err := sqlx.In(sql, ids)
|
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return 0, errors.Wrap(err, "building delete query query")
|
2016-12-09 17:12:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result, err := d.db.Exec(query, args...)
|
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return 0, errors.Wrap(err, "updating delete query")
|
2016-12-09 17:12:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
deleted, err := result.RowsAffected()
|
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return 0, errors.Wrap(err, "fetching delete query rows effected")
|
2016-12-09 17:12:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return uint(deleted), nil
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
// Query returns a single Query identified by id, if such
|
|
|
|
// exists
|
|
|
|
func (d *Datastore) Query(id uint) (*kolide.Query, error) {
|
|
|
|
sql := `
|
2017-01-19 21:46:28 +00:00
|
|
|
SELECT q.*, COALESCE(NULLIF(u.name, ''), u.username) AS author_name
|
2016-12-07 20:22:31 +00:00
|
|
|
FROM queries q
|
|
|
|
LEFT JOIN users u
|
|
|
|
ON q.author_id = u.id
|
|
|
|
WHERE q.id = ?
|
|
|
|
AND NOT q.deleted
|
2016-11-16 13:47:49 +00:00
|
|
|
`
|
|
|
|
query := &kolide.Query{}
|
|
|
|
if err := d.db.Get(query, sql, id); err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting query")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2016-12-06 18:22:28 +00:00
|
|
|
if err := d.loadPacksForQueries([]*kolide.Query{query}); err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "loading packs for queries")
|
2016-12-06 18:22:28 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return query, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListQueries returns a list of queries with sort order and results limit
|
|
|
|
// determined by passed in kolide.ListOptions
|
|
|
|
func (d *Datastore) ListQueries(opt kolide.ListOptions) ([]*kolide.Query, error) {
|
|
|
|
sql := `
|
2017-01-19 21:46:28 +00:00
|
|
|
SELECT q.*, COALESCE(NULLIF(u.name, ''), u.username) AS author_name
|
2016-12-07 20:22:31 +00:00
|
|
|
FROM queries q
|
|
|
|
LEFT JOIN users u
|
|
|
|
ON q.author_id = u.id
|
2016-12-06 18:16:04 +00:00
|
|
|
WHERE saved = true
|
2016-12-07 20:22:31 +00:00
|
|
|
AND NOT q.deleted
|
2016-11-16 13:47:49 +00:00
|
|
|
`
|
|
|
|
sql = appendListOptionsToSQL(sql, opt)
|
|
|
|
results := []*kolide.Query{}
|
|
|
|
|
|
|
|
if err := d.db.Select(&results, sql); err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "listing queries")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2016-12-06 18:22:28 +00:00
|
|
|
if err := d.loadPacksForQueries(results); err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "loading packs for queries")
|
2016-12-06 18:22:28 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return results, nil
|
|
|
|
}
|
2016-12-06 18:22:28 +00:00
|
|
|
|
|
|
|
// loadPacksForQueries loads the packs associated with the provided queries
|
|
|
|
func (d *Datastore) loadPacksForQueries(queries []*kolide.Query) error {
|
2016-12-09 17:12:45 +00:00
|
|
|
if len(queries) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-06 18:22:28 +00:00
|
|
|
sql := `
|
2018-01-03 19:18:05 +00:00
|
|
|
SELECT p.*, sq.query_name AS query_name
|
2016-12-06 18:22:28 +00:00
|
|
|
FROM packs p
|
2016-12-13 22:22:05 +00:00
|
|
|
JOIN scheduled_queries sq
|
|
|
|
ON p.id = sq.pack_id
|
2018-01-03 19:18:05 +00:00
|
|
|
WHERE query_name IN (?)
|
2017-04-18 19:41:51 +00:00
|
|
|
AND NOT p.deleted
|
2016-12-06 18:22:28 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
// Used to map the results
|
2018-01-03 19:18:05 +00:00
|
|
|
name_queries := map[string]*kolide.Query{}
|
2016-12-06 18:22:28 +00:00
|
|
|
// Used for the IN clause
|
2018-01-03 19:18:05 +00:00
|
|
|
names := []string{}
|
2016-12-06 18:22:28 +00:00
|
|
|
for _, q := range queries {
|
|
|
|
q.Packs = make([]kolide.Pack, 0)
|
2018-01-03 19:18:05 +00:00
|
|
|
names = append(names, q.Name)
|
|
|
|
name_queries[q.Name] = q
|
2016-12-06 18:22:28 +00:00
|
|
|
}
|
|
|
|
|
2018-01-03 19:18:05 +00:00
|
|
|
query, args, err := sqlx.In(sql, names)
|
2016-12-06 18:22:28 +00:00
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return errors.Wrap(err, "building query in load packs for queries")
|
2016-12-06 18:22:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rows := []struct {
|
2018-01-03 19:18:05 +00:00
|
|
|
QueryName string `db:"query_name"`
|
2016-12-06 18:22:28 +00:00
|
|
|
kolide.Pack
|
|
|
|
}{}
|
|
|
|
|
|
|
|
err = d.db.Select(&rows, query, args...)
|
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return errors.Wrap(err, "selecting load packs for queries")
|
2016-12-06 18:22:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, row := range rows {
|
2018-01-03 19:18:05 +00:00
|
|
|
q := name_queries[row.QueryName]
|
2016-12-06 18:22:28 +00:00
|
|
|
q.Packs = append(q.Packs, row.Pack)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|