2016-11-16 13:47:49 +00:00
|
|
|
package mysql
|
|
|
|
|
|
|
|
import (
|
2021-09-14 12:11:07 +00:00
|
|
|
"context"
|
2017-01-11 20:33:30 +00:00
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
2021-06-18 16:43:16 +00:00
|
|
|
"strings"
|
2017-01-11 20:33:30 +00:00
|
|
|
|
2021-11-15 14:11:38 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2021-01-26 00:26:14 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2016-11-16 13:47:49 +00:00
|
|
|
)
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ApplyPackSpecs(ctx context.Context, specs []*fleet.PackSpec) (err error) {
|
2021-09-14 14:44:02 +00:00
|
|
|
err = d.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2020-03-11 01:14:02 +00:00
|
|
|
for _, spec := range specs {
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := applyPackSpecDB(ctx, tx, spec); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrapf(ctx, err, "applying pack '%s'", spec.Name)
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
return nil
|
|
|
|
})
|
2018-01-03 19:18:05 +00:00
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
return err
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func applyPackSpecDB(ctx context.Context, tx sqlx.ExtContext, spec *fleet.PackSpec) error {
|
2018-05-14 18:23:38 +00:00
|
|
|
if spec.Name == "" {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.New(ctx, "pack name must not be empty")
|
2018-05-14 18:23:38 +00:00
|
|
|
}
|
2018-01-03 19:18:05 +00:00
|
|
|
// Insert/update pack
|
|
|
|
query := `
|
2020-10-21 23:29:27 +00:00
|
|
|
INSERT INTO packs (name, description, platform, disabled)
|
|
|
|
VALUES (?, ?, ?, ?)
|
2018-01-03 19:18:05 +00:00
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
|
name = VALUES(name),
|
|
|
|
description = VALUES(description),
|
|
|
|
platform = VALUES(platform),
|
2020-10-22 17:51:26 +00:00
|
|
|
disabled = VALUES(disabled)
|
2018-01-03 19:18:05 +00:00
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, query, spec.Name, spec.Description, spec.Platform, spec.Disabled); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "insert/update pack")
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get Pack ID
|
|
|
|
// This is necessary because MySQL last_insert_id does not return a value
|
|
|
|
// if no update was made.
|
|
|
|
var packID uint
|
|
|
|
query = "SELECT id FROM packs WHERE name = ?"
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.GetContext(ctx, tx, &packID, query, spec.Name); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "getting pack ID")
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete existing scheduled queries for pack
|
|
|
|
query = "DELETE FROM scheduled_queries WHERE pack_id = ?"
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, query, packID); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "delete existing scheduled queries")
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Insert new scheduled queries for pack
|
|
|
|
for _, q := range spec.Queries {
|
2019-01-17 23:59:42 +00:00
|
|
|
// Default to query name if scheduled query name is not specified.
|
|
|
|
if q.Name == "" {
|
|
|
|
q.Name = q.QueryName
|
|
|
|
}
|
2018-01-03 19:18:05 +00:00
|
|
|
query = `
|
|
|
|
INSERT INTO scheduled_queries (
|
|
|
|
pack_id, query_name, name, description, ` + "`interval`" + `,
|
2021-01-26 00:26:14 +00:00
|
|
|
snapshot, removed, shard, platform, version, denylist
|
2018-01-03 19:18:05 +00:00
|
|
|
)
|
|
|
|
VALUES (
|
|
|
|
?, ?, ?, ?, ?,
|
2021-01-26 00:26:14 +00:00
|
|
|
?, ?, ?, ?, ?, ?
|
2018-01-03 19:18:05 +00:00
|
|
|
)
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
_, err := tx.ExecContext(ctx, query,
|
2018-01-03 19:18:05 +00:00
|
|
|
packID, q.QueryName, q.Name, q.Description, q.Interval,
|
2021-01-26 00:26:14 +00:00
|
|
|
q.Snapshot, q.Removed, q.Shard, q.Platform, q.Version, q.Denylist,
|
2018-01-03 19:18:05 +00:00
|
|
|
)
|
|
|
|
switch {
|
|
|
|
case isChildForeignKeyError(err):
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Errorf(ctx, "cannot schedule unknown query '%s'", q.QueryName)
|
2018-01-03 19:18:05 +00:00
|
|
|
case err != nil:
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrapf(ctx, err, "adding query %s referencing %s", q.Name, q.QueryName)
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete existing targets
|
|
|
|
query = "DELETE FROM pack_targets WHERE pack_id = ?"
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, query, packID); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "delete existing targets")
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Insert targets
|
|
|
|
for _, l := range spec.Targets.Labels {
|
|
|
|
query = `
|
|
|
|
INSERT INTO pack_targets (pack_id, type, target_id)
|
|
|
|
VALUES (?, ?, (SELECT id FROM labels WHERE name = ?))
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, query, packID, fleet.TargetLabel, l); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "adding label to pack")
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) GetPackSpecs(ctx context.Context) (specs []*fleet.PackSpec, err error) {
|
2021-09-14 14:44:02 +00:00
|
|
|
err = d.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2020-03-11 01:14:02 +00:00
|
|
|
// Get basic specs
|
2021-08-10 13:43:27 +00:00
|
|
|
query := "SELECT id, name, description, platform, disabled FROM packs WHERE pack_type IS NULL OR pack_type = ''"
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, tx, &specs, query); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get packs")
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
// Load targets
|
|
|
|
for _, spec := range specs {
|
|
|
|
query = `
|
2018-01-03 19:18:05 +00:00
|
|
|
SELECT l.name
|
|
|
|
FROM labels l JOIN pack_targets pt
|
|
|
|
WHERE pack_id = ? AND pt.type = ? AND pt.target_id = l.id
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, tx, &spec.Targets.Labels, query, spec.ID, fleet.TargetLabel); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get pack targets")
|
2020-03-11 01:14:02 +00:00
|
|
|
}
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
// Load queries
|
|
|
|
for _, spec := range specs {
|
|
|
|
query = `
|
2018-01-03 19:18:05 +00:00
|
|
|
SELECT
|
|
|
|
query_name, name, description, ` + "`interval`" + `,
|
2021-01-26 00:26:14 +00:00
|
|
|
snapshot, removed, shard, platform, version, denylist
|
2018-01-03 19:18:05 +00:00
|
|
|
FROM scheduled_queries
|
|
|
|
WHERE pack_id = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, tx, &spec.Queries, query, spec.ID); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get pack queries")
|
2020-03-11 01:14:02 +00:00
|
|
|
}
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2018-01-03 19:18:05 +00:00
|
|
|
if err != nil {
|
2020-03-11 01:14:02 +00:00
|
|
|
return nil, err
|
2018-01-03 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return specs, nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) GetPackSpec(ctx context.Context, name string) (spec *fleet.PackSpec, err error) {
|
2021-09-14 14:44:02 +00:00
|
|
|
err = d.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2020-03-11 01:14:02 +00:00
|
|
|
// Get basic spec
|
2021-06-06 22:07:29 +00:00
|
|
|
var specs []*fleet.PackSpec
|
2020-10-21 23:29:27 +00:00
|
|
|
query := "SELECT id, name, description, platform, disabled FROM packs WHERE name = ?"
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, tx, &specs, query, name); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get packs")
|
2020-03-11 01:14:02 +00:00
|
|
|
}
|
|
|
|
if len(specs) == 0 {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, notFound("Pack").WithName(name))
|
2020-03-11 01:14:02 +00:00
|
|
|
}
|
|
|
|
if len(specs) > 1 {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Errorf(ctx, "expected 1 pack row, got %d", len(specs))
|
2018-05-08 01:54:29 +00:00
|
|
|
}
|
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
spec = specs[0]
|
2018-05-08 01:54:29 +00:00
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
// Load targets
|
|
|
|
query = `
|
2018-05-08 01:54:29 +00:00
|
|
|
SELECT l.name
|
|
|
|
FROM labels l JOIN pack_targets pt
|
|
|
|
WHERE pack_id = ? AND pt.type = ? AND pt.target_id = l.id
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, tx, &spec.Targets.Labels, query, spec.ID, fleet.TargetLabel); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get pack targets")
|
2020-03-11 01:14:02 +00:00
|
|
|
}
|
2018-05-08 01:54:29 +00:00
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
// Load queries
|
|
|
|
query = `
|
2018-05-08 01:54:29 +00:00
|
|
|
SELECT
|
|
|
|
query_name, name, description, ` + "`interval`" + `,
|
2021-01-26 00:26:14 +00:00
|
|
|
snapshot, removed, shard, platform, version, denylist
|
2018-05-08 01:54:29 +00:00
|
|
|
FROM scheduled_queries
|
|
|
|
WHERE pack_id = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, tx, &spec.Queries, query, spec.ID); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get pack queries")
|
2020-03-11 01:14:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2018-05-08 01:54:29 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2020-03-11 01:14:02 +00:00
|
|
|
return nil, err
|
2018-05-08 01:54:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return spec, nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) PackByName(ctx context.Context, name string, opts ...fleet.OptionalArg) (*fleet.Pack, bool, error) {
|
2017-01-13 18:35:25 +00:00
|
|
|
sqlStatement := `
|
|
|
|
SELECT *
|
|
|
|
FROM packs
|
2020-10-22 17:51:26 +00:00
|
|
|
WHERE name = ?
|
2017-01-13 18:35:25 +00:00
|
|
|
`
|
2021-06-06 22:07:29 +00:00
|
|
|
var pack fleet.Pack
|
2021-09-14 14:44:02 +00:00
|
|
|
err := sqlx.GetContext(ctx, d.reader, &pack, sqlStatement, name)
|
2017-01-13 18:35:25 +00:00
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return nil, false, nil
|
|
|
|
}
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, false, ctxerr.Wrap(ctx, err, "fetch pack by name")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := loadPackTargetsDB(ctx, d.reader, &pack); err != nil {
|
2021-06-18 16:43:16 +00:00
|
|
|
return nil, false, err
|
2017-01-13 18:35:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &pack, true, nil
|
|
|
|
}
|
|
|
|
|
2018-06-15 14:13:11 +00:00
|
|
|
// NewPack creates a new Pack
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) NewPack(ctx context.Context, pack *fleet.Pack, opts ...fleet.OptionalArg) (*fleet.Pack, error) {
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := d.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2021-06-18 16:43:16 +00:00
|
|
|
query := `
|
|
|
|
INSERT INTO packs
|
|
|
|
(name, description, platform, disabled)
|
|
|
|
VALUES ( ?, ?, ?, ? )
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
result, err := tx.ExecContext(ctx, query, pack.Name, pack.Description, pack.Platform, pack.Disabled)
|
2021-06-18 16:43:16 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "insert pack")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
2018-06-15 14:13:11 +00:00
|
|
|
|
2021-06-18 16:43:16 +00:00
|
|
|
id, _ := result.LastInsertId()
|
|
|
|
pack.ID = uint(id)
|
2020-10-22 17:51:26 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := replacePackTargetsDB(ctx, tx, pack); err != nil {
|
2021-06-18 16:43:16 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-06-15 14:13:11 +00:00
|
|
|
|
2021-06-18 16:43:16 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-06-15 14:13:11 +00:00
|
|
|
return pack, nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func replacePackTargetsDB(ctx context.Context, tx sqlx.ExecerContext, pack *fleet.Pack) error {
|
2021-06-18 16:43:16 +00:00
|
|
|
sql := `DELETE FROM pack_targets WHERE pack_id = ?`
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, sql, pack.ID); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "delete pack targets")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Insert hosts
|
|
|
|
if len(pack.HostIDs) > 0 {
|
|
|
|
var args []interface{}
|
|
|
|
for _, id := range pack.HostIDs {
|
|
|
|
args = append(args, pack.ID, fleet.TargetHost, id)
|
|
|
|
}
|
|
|
|
values := strings.TrimSuffix(
|
|
|
|
strings.Repeat("(?,?,?),", len(pack.HostIDs)),
|
|
|
|
",",
|
|
|
|
)
|
|
|
|
sql = fmt.Sprintf(`
|
|
|
|
INSERT INTO pack_targets (pack_id, type, target_id)
|
|
|
|
VALUES %s
|
|
|
|
`, values)
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, sql, args...); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "insert host targets")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert labels
|
|
|
|
if len(pack.LabelIDs) > 0 {
|
|
|
|
var args []interface{}
|
|
|
|
for _, id := range pack.LabelIDs {
|
|
|
|
args = append(args, pack.ID, fleet.TargetLabel, id)
|
|
|
|
}
|
|
|
|
values := strings.TrimSuffix(
|
|
|
|
strings.Repeat("(?,?,?),", len(pack.LabelIDs)),
|
|
|
|
",",
|
|
|
|
)
|
|
|
|
sql = fmt.Sprintf(`
|
|
|
|
INSERT INTO pack_targets (pack_id, type, target_id)
|
|
|
|
VALUES %s
|
|
|
|
`, values)
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, sql, args...); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "insert label targets")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert teams
|
|
|
|
if len(pack.TeamIDs) > 0 {
|
|
|
|
var args []interface{}
|
|
|
|
for _, id := range pack.TeamIDs {
|
|
|
|
args = append(args, pack.ID, fleet.TargetTeam, id)
|
|
|
|
}
|
|
|
|
values := strings.TrimSuffix(
|
|
|
|
strings.Repeat("(?,?,?),", len(pack.TeamIDs)),
|
|
|
|
",",
|
|
|
|
)
|
|
|
|
sql = fmt.Sprintf(`
|
|
|
|
INSERT INTO pack_targets (pack_id, type, target_id)
|
|
|
|
VALUES %s
|
|
|
|
`, values)
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, sql, args...); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "insert team targets")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func loadPackTargetsDB(ctx context.Context, q sqlx.QueryerContext, pack *fleet.Pack) error {
|
2021-12-14 05:50:24 +00:00
|
|
|
var targets []fleet.Target
|
|
|
|
sql := `
|
|
|
|
SELECT type, target_id,
|
|
|
|
COALESCE(
|
|
|
|
CASE
|
|
|
|
WHEN type = ? THEN (SELECT hostname FROM hosts WHERE id = target_id)
|
|
|
|
WHEN type = ? THEN (SELECT name FROM teams WHERE id = target_id)
|
|
|
|
WHEN type = ? THEN (SELECT name FROM labels WHERE id = target_id)
|
|
|
|
END
|
|
|
|
, '') AS display_text
|
|
|
|
FROM pack_targets
|
|
|
|
WHERE pack_id = ?`
|
|
|
|
if err := sqlx.SelectContext(
|
|
|
|
ctx, q, &targets, sql,
|
|
|
|
fleet.TargetHost, fleet.TargetTeam, fleet.TargetLabel, pack.ID,
|
|
|
|
); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "select pack targets")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pack.HostIDs, pack.LabelIDs, pack.TeamIDs = []uint{}, []uint{}, []uint{}
|
2021-12-14 05:50:24 +00:00
|
|
|
pack.Hosts, pack.Labels, pack.Teams = []fleet.Target{}, []fleet.Target{}, []fleet.Target{}
|
2021-06-18 16:43:16 +00:00
|
|
|
for _, target := range targets {
|
|
|
|
switch target.Type {
|
|
|
|
case fleet.TargetHost:
|
|
|
|
pack.HostIDs = append(pack.HostIDs, target.TargetID)
|
2021-12-14 05:50:24 +00:00
|
|
|
pack.Hosts = append(pack.Hosts, target)
|
2021-06-18 16:43:16 +00:00
|
|
|
case fleet.TargetLabel:
|
|
|
|
pack.LabelIDs = append(pack.LabelIDs, target.TargetID)
|
2021-12-14 05:50:24 +00:00
|
|
|
pack.Labels = append(pack.Labels, target)
|
2021-06-18 16:43:16 +00:00
|
|
|
case fleet.TargetTeam:
|
|
|
|
pack.TeamIDs = append(pack.TeamIDs, target.TargetID)
|
2021-12-14 05:50:24 +00:00
|
|
|
pack.Teams = append(pack.Teams, target)
|
2021-06-18 16:43:16 +00:00
|
|
|
default:
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Errorf(ctx, "unknown target type: %d", target.Type)
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-06-15 14:13:11 +00:00
|
|
|
// SavePack stores changes to pack
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) SavePack(ctx context.Context, pack *fleet.Pack) error {
|
2021-09-14 14:44:02 +00:00
|
|
|
return d.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2021-06-18 16:43:16 +00:00
|
|
|
query := `
|
2018-06-15 14:13:11 +00:00
|
|
|
UPDATE packs
|
|
|
|
SET name = ?, platform = ?, disabled = ?, description = ?
|
2020-10-22 17:51:26 +00:00
|
|
|
WHERE id = ?
|
2018-06-15 14:13:11 +00:00
|
|
|
`
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
results, err := tx.ExecContext(ctx, query, pack.Name, pack.Platform, pack.Disabled, pack.Description, pack.ID)
|
2021-06-18 16:43:16 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "updating pack")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
rowsAffected, err := results.RowsAffected()
|
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "rows affected updating packs")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, notFound("Pack").WithID(pack.ID))
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
return replacePackTargetsDB(ctx, tx, pack)
|
2021-06-18 16:43:16 +00:00
|
|
|
})
|
2018-06-15 14:13:11 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// DeletePack deletes a fleet.Pack so that it won't show up in results.
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) DeletePack(ctx context.Context, name string) error {
|
2021-09-20 17:47:06 +00:00
|
|
|
return d.deleteEntityByName(ctx, packsTable, name)
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// Pack fetch fleet.Pack with matching ID
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) Pack(ctx context.Context, pid uint) (*fleet.Pack, error) {
|
2021-09-14 14:44:02 +00:00
|
|
|
return packDB(ctx, d.reader, pid)
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func packDB(ctx context.Context, q sqlx.QueryerContext, pid uint) (*fleet.Pack, error) {
|
2020-10-22 17:51:26 +00:00
|
|
|
query := `SELECT * FROM packs WHERE id = ?`
|
2021-06-06 22:07:29 +00:00
|
|
|
pack := &fleet.Pack{}
|
2021-09-14 14:44:02 +00:00
|
|
|
err := sqlx.GetContext(ctx, q, pack, query, pid)
|
2017-01-11 20:33:30 +00:00
|
|
|
if err == sql.ErrNoRows {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, notFound("Pack").WithID(pid))
|
2017-01-11 20:33:30 +00:00
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "get pack")
|
2021-06-18 16:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := loadPackTargetsDB(ctx, q, pack); err != nil {
|
2021-06-18 16:43:16 +00:00
|
|
|
return nil, err
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return pack, nil
|
|
|
|
}
|
|
|
|
|
2021-07-16 13:15:15 +00:00
|
|
|
// EnsureGlobalPack gets or inserts a pack with type global
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) EnsureGlobalPack(ctx context.Context) (*fleet.Pack, error) {
|
2021-07-16 13:15:15 +00:00
|
|
|
pack := &fleet.Pack{}
|
2021-09-14 14:44:02 +00:00
|
|
|
err := d.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2021-09-08 18:43:22 +00:00
|
|
|
// read from primary as we will create the pack if it doesn't exist
|
2021-09-14 14:44:02 +00:00
|
|
|
err := sqlx.GetContext(ctx, tx, pack, `SELECT * FROM packs WHERE pack_type = 'global'`)
|
2021-09-08 18:43:22 +00:00
|
|
|
if err == sql.ErrNoRows {
|
2021-09-14 14:44:02 +00:00
|
|
|
pack, err = insertNewGlobalPackDB(ctx, tx)
|
2021-09-08 18:43:22 +00:00
|
|
|
return err
|
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get pack")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
2021-07-16 13:15:15 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
return loadPackTargetsDB(ctx, tx, pack)
|
2021-09-08 18:43:22 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2021-07-16 13:15:15 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return pack, nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func insertNewGlobalPackDB(ctx context.Context, q sqlx.ExtContext) (*fleet.Pack, error) {
|
2021-07-16 13:15:15 +00:00
|
|
|
var packID uint
|
2021-09-14 14:44:02 +00:00
|
|
|
res, err := q.ExecContext(ctx,
|
2021-09-08 18:43:22 +00:00
|
|
|
`INSERT INTO packs (name, description, platform, pack_type) VALUES ('Global', 'Global pack', '','global')`,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "insert pack")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
|
|
|
packId, err := res.LastInsertId()
|
2021-08-03 13:33:27 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "last insert id")
|
2021-08-03 13:33:27 +00:00
|
|
|
}
|
2021-09-08 18:43:22 +00:00
|
|
|
packID = uint(packId)
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := q.ExecContext(ctx,
|
2021-09-08 18:43:22 +00:00
|
|
|
`INSERT INTO pack_targets (pack_id, type, target_id) VALUES (?, ?, (SELECT id FROM labels WHERE name = ?))`,
|
|
|
|
packID, fleet.TargetLabel, "All Hosts",
|
|
|
|
); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "adding label to pack")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
2021-08-03 13:33:27 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
return packDB(ctx, q, packID)
|
2021-08-03 13:33:27 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) EnsureTeamPack(ctx context.Context, teamID uint) (*fleet.Pack, error) {
|
2021-08-03 13:33:27 +00:00
|
|
|
pack := &fleet.Pack{}
|
2021-09-14 14:44:02 +00:00
|
|
|
err := d.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
|
|
|
t, err := teamDB(ctx, tx, teamID)
|
2021-09-08 18:43:22 +00:00
|
|
|
if err != nil || t == nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "Error finding team")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
2021-08-03 13:33:27 +00:00
|
|
|
|
2021-09-08 18:43:22 +00:00
|
|
|
teamType := fmt.Sprintf("team-%d", teamID)
|
|
|
|
// read from primary as we will create the team pack if it doesn't exist
|
2021-09-14 14:44:02 +00:00
|
|
|
err = sqlx.GetContext(ctx, tx, pack, `SELECT * FROM packs WHERE pack_type = ?`, teamType)
|
2021-09-08 18:43:22 +00:00
|
|
|
if err == sql.ErrNoRows {
|
2021-09-14 14:44:02 +00:00
|
|
|
pack, err = insertNewTeamPackDB(ctx, tx, t)
|
2021-09-08 18:43:22 +00:00
|
|
|
return err
|
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get pack")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
2021-08-03 13:33:27 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := loadPackTargetsDB(ctx, tx, pack); err != nil {
|
2021-09-08 18:43:22 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
2021-08-03 13:33:27 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return pack, nil
|
|
|
|
}
|
|
|
|
|
2021-08-23 17:23:47 +00:00
|
|
|
func teamScheduleName(team *fleet.Team) string {
|
|
|
|
return fmt.Sprintf("Team: %s", team.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func teamSchedulePackType(team *fleet.Team) string {
|
|
|
|
return fmt.Sprintf("team-%d", team.ID)
|
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func insertNewTeamPackDB(ctx context.Context, q sqlx.ExtContext, team *fleet.Team) (*fleet.Pack, error) {
|
2021-08-03 13:33:27 +00:00
|
|
|
var packID uint
|
2021-09-14 14:44:02 +00:00
|
|
|
res, err := q.ExecContext(ctx,
|
2021-09-08 18:43:22 +00:00
|
|
|
`INSERT INTO packs (name, description, platform, pack_type)
|
2021-08-03 13:33:27 +00:00
|
|
|
VALUES (?, 'Schedule additional queries for all hosts assigned to this team.', '',?)`,
|
2021-09-08 18:43:22 +00:00
|
|
|
teamScheduleName(team), teamSchedulePackType(team),
|
|
|
|
)
|
2021-08-03 13:33:27 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "insert team pack")
|
2021-08-03 13:33:27 +00:00
|
|
|
}
|
2021-09-08 18:43:22 +00:00
|
|
|
packId, err := res.LastInsertId()
|
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "last insert id")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
|
|
|
packID = uint(packId)
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := q.ExecContext(ctx,
|
2021-09-08 18:43:22 +00:00
|
|
|
`INSERT INTO pack_targets (pack_id, type, target_id) VALUES (?, ?, ?)`,
|
|
|
|
packID, fleet.TargetTeam, team.ID,
|
|
|
|
); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "adding team id target to pack")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
2021-09-14 14:44:02 +00:00
|
|
|
return packDB(ctx, q, packID)
|
2021-07-16 13:15:15 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// ListPacks returns all fleet.Pack records limited and sorted by fleet.ListOptions
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ListPacks(ctx context.Context, opt fleet.PackListOptions) ([]*fleet.Pack, error) {
|
2021-08-10 13:43:27 +00:00
|
|
|
query := `SELECT * FROM packs WHERE pack_type IS NULL OR pack_type = ''`
|
|
|
|
if opt.IncludeSystemPacks {
|
|
|
|
query = `SELECT * FROM packs`
|
|
|
|
}
|
|
|
|
var packs []*fleet.Pack
|
2021-09-14 14:44:02 +00:00
|
|
|
err := sqlx.SelectContext(ctx, d.reader, &packs, appendListOptionsToSQL(query, opt.ListOptions))
|
2017-01-11 20:33:30 +00:00
|
|
|
if err != nil && err != sql.ErrNoRows {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "listing packs")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-18 16:43:16 +00:00
|
|
|
for _, pack := range packs {
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := loadPackTargetsDB(ctx, d.reader, pack); err != nil {
|
2021-06-18 16:43:16 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-18 16:43:16 +00:00
|
|
|
return packs, nil
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ListPacksForHost(ctx context.Context, hid uint) ([]*fleet.Pack, error) {
|
2021-11-12 11:18:25 +00:00
|
|
|
return listPacksForHost(ctx, d.reader, hid)
|
|
|
|
}
|
|
|
|
|
|
|
|
// listPacksForHost returns all the packs that are configured to run on the given host.
|
|
|
|
func listPacksForHost(ctx context.Context, db sqlx.QueryerContext, hid uint) ([]*fleet.Pack, error) {
|
2017-01-11 20:33:30 +00:00
|
|
|
query := `
|
2021-11-12 11:18:25 +00:00
|
|
|
SELECT DISTINCT packs.* FROM (
|
|
|
|
(
|
|
|
|
SELECT p.* FROM packs p
|
2018-01-10 19:38:20 +00:00
|
|
|
JOIN pack_targets pt
|
2020-04-07 01:10:20 +00:00
|
|
|
JOIN label_membership lm
|
2018-01-10 19:38:20 +00:00
|
|
|
ON (
|
2021-11-12 11:18:25 +00:00
|
|
|
p.id = pt.pack_id
|
|
|
|
AND pt.target_id = lm.label_id
|
|
|
|
AND pt.type = ?
|
2018-01-10 19:38:20 +00:00
|
|
|
)
|
2021-11-12 11:18:25 +00:00
|
|
|
WHERE lm.host_id = ? AND NOT p.disabled
|
|
|
|
)
|
|
|
|
UNION ALL
|
|
|
|
(
|
|
|
|
SELECT p.* FROM packs p
|
2018-08-13 17:07:10 +00:00
|
|
|
JOIN pack_targets pt
|
2021-11-12 11:18:25 +00:00
|
|
|
ON (p.id = pt.pack_id AND pt.type = ? AND pt.target_id = ?)
|
|
|
|
)
|
|
|
|
UNION ALL
|
|
|
|
(
|
|
|
|
SELECT p.*
|
2021-06-18 16:43:16 +00:00
|
|
|
FROM packs p
|
|
|
|
JOIN pack_targets pt
|
|
|
|
ON (p.id = pt.pack_id AND pt.type = ? AND pt.target_id = (SELECT team_id FROM hosts WHERE id = ?)))
|
2021-11-12 11:18:25 +00:00
|
|
|
) packs`
|
2021-06-06 22:07:29 +00:00
|
|
|
packs := []*fleet.Pack{}
|
2021-11-12 11:18:25 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, db, &packs, query,
|
|
|
|
fleet.TargetLabel, hid, fleet.TargetHost, hid, fleet.TargetTeam, hid,
|
|
|
|
); err != nil && err != sql.ErrNoRows {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "listing hosts in pack")
|
Add host_ids and label_ids fields to the packs API (#737)
This PR adds the `host_ids` and `label_ids` field to the packs HTTP API so that one can operate on the hosts/labels which a pack is scheduled to be executed on. This replaces (and deletes) the `/api/v1/kolide/packs/123/labels/456` API in favor of `PATCH /api/v1/packs/123` and specifying the `label_ids` field. This also allows for bulk operations.
Consider the following API examples:
## Creating a pack with a known set of hosts and labels
The key addition is the `host_ids` and `label_ids` field in both the request and the response.
### Request
```
POST /api/v1/kolide/packs
```
```json
{
"name": "My new pack",
"description": "The newest of the packs",
"host_ids": [1, 2, 3],
"label_ids": [1, 3, 5]
}
```
### Response
```json
{
"pack": {
"id": 123,
"name": "My new pack",
"description": "The newest of the packs",
"platform": "",
"created_by": 1,
"disabled": false,
"query_count": 0,
"total_hosts_count": 5,
"host_ids": [1, 2, 3],
"label_ids": [1, 3, 5]
}
}
```
## Modifying the hosts and/or labels that a pack is scheduled to execute on
### Request
```
PATCH /api/v1/kolide/packs/123
```
```json
{
"host_ids": [1, 2, 3, 4, 5],
"label_ids": [1, 3, 5, 7]
}
```
### Response
```json
{
"pack": {
"id": 123,
"name": "My new pack",
"description": "The newest of the packs",
"platform": "",
"created_by": 1,
"disabled": false,
"query_count": 0,
"total_hosts_count": 5,
"host_ids": [1, 2, 3, 4, 5],
"label_ids": [1, 3, 5, 7]
}
}
```
close #633
2017-01-03 17:32:06 +00:00
|
|
|
}
|
2018-01-10 19:38:20 +00:00
|
|
|
return packs, nil
|
Add host_ids and label_ids fields to the packs API (#737)
This PR adds the `host_ids` and `label_ids` field to the packs HTTP API so that one can operate on the hosts/labels which a pack is scheduled to be executed on. This replaces (and deletes) the `/api/v1/kolide/packs/123/labels/456` API in favor of `PATCH /api/v1/packs/123` and specifying the `label_ids` field. This also allows for bulk operations.
Consider the following API examples:
## Creating a pack with a known set of hosts and labels
The key addition is the `host_ids` and `label_ids` field in both the request and the response.
### Request
```
POST /api/v1/kolide/packs
```
```json
{
"name": "My new pack",
"description": "The newest of the packs",
"host_ids": [1, 2, 3],
"label_ids": [1, 3, 5]
}
```
### Response
```json
{
"pack": {
"id": 123,
"name": "My new pack",
"description": "The newest of the packs",
"platform": "",
"created_by": 1,
"disabled": false,
"query_count": 0,
"total_hosts_count": 5,
"host_ids": [1, 2, 3],
"label_ids": [1, 3, 5]
}
}
```
## Modifying the hosts and/or labels that a pack is scheduled to execute on
### Request
```
PATCH /api/v1/kolide/packs/123
```
```json
{
"host_ids": [1, 2, 3, 4, 5],
"label_ids": [1, 3, 5, 7]
}
```
### Response
```json
{
"pack": {
"id": 123,
"name": "My new pack",
"description": "The newest of the packs",
"platform": "",
"created_by": 1,
"disabled": false,
"query_count": 0,
"total_hosts_count": 5,
"host_ids": [1, 2, 3, 4, 5],
"label_ids": [1, 3, 5, 7]
}
}
```
close #633
2017-01-03 17:32:06 +00:00
|
|
|
}
|