2016-11-16 13:47:49 +00:00
|
|
|
package mysql
|
|
|
|
|
|
|
|
import (
|
2021-09-14 12:11:07 +00:00
|
|
|
"context"
|
2016-11-16 13:47:49 +00:00
|
|
|
"database/sql"
|
2021-05-25 04:34:08 +00:00
|
|
|
"fmt"
|
2021-06-21 23:10:24 +00:00
|
|
|
"sort"
|
2020-04-07 01:10:20 +00:00
|
|
|
"strings"
|
2016-11-16 13:47:49 +00:00
|
|
|
"time"
|
|
|
|
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2021-02-18 20:52:43 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2017-01-13 18:35:25 +00:00
|
|
|
"github.com/pkg/errors"
|
2016-11-16 13:47:49 +00:00
|
|
|
)
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ApplyLabelSpecs(ctx context.Context, specs []*fleet.LabelSpec) (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
|
|
|
sql := `
|
2016-11-16 13:47:49 +00:00
|
|
|
INSERT INTO labels (
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
query,
|
2016-11-25 18:08:22 +00:00
|
|
|
platform,
|
2020-04-07 22:12:32 +00:00
|
|
|
label_type,
|
|
|
|
label_membership_type
|
2020-07-01 17:51:34 +00:00
|
|
|
) VALUES ( ?, ?, ?, ?, ?, ?)
|
2018-01-10 19:38:20 +00:00
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
|
name = VALUES(name),
|
|
|
|
description = VALUES(description),
|
|
|
|
query = VALUES(query),
|
|
|
|
platform = VALUES(platform),
|
|
|
|
label_type = VALUES(label_type),
|
2020-10-22 17:51:26 +00:00
|
|
|
label_membership_type = VALUES(label_membership_type)
|
2016-11-16 13:47:49 +00:00
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
|
|
|
|
prepTx, ok := tx.(sqlx.PreparerContext)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("tx in ApplyLabelSpecs is not a sqlx.PreparerContext")
|
|
|
|
}
|
|
|
|
stmt, err := prepTx.PrepareContext(ctx, sql)
|
2018-01-10 19:38:20 +00:00
|
|
|
if err != nil {
|
2020-03-11 01:14:02 +00:00
|
|
|
return errors.Wrap(err, "prepare ApplyLabelSpecs insert")
|
2018-01-10 19:38:20 +00:00
|
|
|
}
|
2021-08-23 20:33:41 +00:00
|
|
|
defer stmt.Close()
|
2018-01-10 19:38:20 +00:00
|
|
|
|
2020-03-11 01:14:02 +00:00
|
|
|
for _, s := range specs {
|
|
|
|
if s.Name == "" {
|
|
|
|
return errors.New("label name must not be empty")
|
|
|
|
}
|
2021-09-14 14:44:02 +00:00
|
|
|
_, err := stmt.ExecContext(ctx, s.Name, s.Description, s.Query, s.Platform, s.LabelType, s.LabelMembershipType)
|
2020-03-11 01:14:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "exec ApplyLabelSpecs insert")
|
|
|
|
}
|
2020-04-07 22:12:32 +00:00
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
if s.LabelType == fleet.LabelTypeBuiltIn ||
|
|
|
|
s.LabelMembershipType != fleet.LabelMembershipTypeManual {
|
2020-04-07 22:12:32 +00:00
|
|
|
// No need to update membership
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var labelID uint
|
|
|
|
sql = `
|
|
|
|
SELECT id from labels WHERE name = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.GetContext(ctx, tx, &labelID, sql, s.Name); err != nil {
|
2020-04-07 22:12:32 +00:00
|
|
|
return errors.Wrap(err, "get label ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
sql = `
|
|
|
|
DELETE FROM label_membership WHERE label_id = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
_, err = tx.ExecContext(ctx, sql, labelID)
|
2020-04-07 22:12:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "clear membership for ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(s.Hosts) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Split hostnames into batches to avoid parameter limit in MySQL.
|
|
|
|
for _, hostnames := range batchHostnames(s.Hosts) {
|
|
|
|
// Use ignore because duplicate hostnames could appear in
|
|
|
|
// different batches and would result in duplicate key errors.
|
|
|
|
sql = `
|
2021-06-24 00:32:19 +00:00
|
|
|
INSERT IGNORE INTO label_membership (label_id, host_id) (SELECT ?, id FROM hosts where hostname IN (?))
|
2020-04-07 22:12:32 +00:00
|
|
|
`
|
|
|
|
sql, args, err := sqlx.In(sql, labelID, hostnames)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "build membership IN statement")
|
|
|
|
}
|
2021-09-14 14:44:02 +00:00
|
|
|
_, err = tx.ExecContext(ctx, sql, args...)
|
2020-04-07 22:12:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "execute membership INSERT")
|
|
|
|
}
|
|
|
|
}
|
2020-03-11 01:14:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return errors.Wrap(err, "ApplyLabelSpecs transaction")
|
2018-01-10 19:38:20 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 22:12:32 +00:00
|
|
|
func batchHostnames(hostnames []string) [][]string {
|
|
|
|
// Split hostnames into batches so that they can all be inserted without
|
|
|
|
// overflowing the MySQL max number of parameters (somewhere around 65,000
|
|
|
|
// but not well documented). Algorithm from
|
|
|
|
// https://github.com/golang/go/wiki/SliceTricks#batching-with-minimal-allocation
|
|
|
|
const batchSize = 50000 // Large, but well under the undocumented limit
|
|
|
|
batches := make([][]string, 0, (len(hostnames)+batchSize-1)/batchSize)
|
|
|
|
|
|
|
|
for batchSize < len(hostnames) {
|
|
|
|
hostnames, batches = hostnames[batchSize:], append(batches, hostnames[0:batchSize:batchSize])
|
|
|
|
}
|
|
|
|
batches = append(batches, hostnames)
|
|
|
|
return batches
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) GetLabelSpecs(ctx context.Context) ([]*fleet.LabelSpec, error) {
|
2021-06-06 22:07:29 +00:00
|
|
|
var specs []*fleet.LabelSpec
|
2018-01-10 19:38:20 +00:00
|
|
|
// Get basic specs
|
2021-06-22 23:41:25 +00:00
|
|
|
query := "SELECT id, name, description, query, platform, label_type, label_membership_type FROM labels"
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, d.reader, &specs, query); err != nil {
|
2018-01-10 19:38:20 +00:00
|
|
|
return nil, errors.Wrap(err, "get labels")
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2020-04-07 22:12:32 +00:00
|
|
|
for _, spec := range specs {
|
2021-06-06 22:07:29 +00:00
|
|
|
if spec.LabelType != fleet.LabelTypeBuiltIn &&
|
|
|
|
spec.LabelMembershipType == fleet.LabelMembershipTypeManual {
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := d.getLabelHostnames(ctx, spec); err != nil {
|
2020-04-07 22:12:32 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 19:38:20 +00:00
|
|
|
return specs, nil
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) GetLabelSpec(ctx context.Context, name string) (*fleet.LabelSpec, error) {
|
2021-06-06 22:07:29 +00:00
|
|
|
var specs []*fleet.LabelSpec
|
2018-05-08 01:54:29 +00:00
|
|
|
query := `
|
2020-04-07 22:12:32 +00:00
|
|
|
SELECT name, description, query, platform, label_type, label_membership_type
|
2018-05-08 01:54:29 +00:00
|
|
|
FROM labels
|
|
|
|
WHERE name = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, d.reader, &specs, query, name); err != nil {
|
2018-05-08 01:54:29 +00:00
|
|
|
return nil, errors.Wrap(err, "get label")
|
|
|
|
}
|
2018-05-09 23:54:42 +00:00
|
|
|
if len(specs) == 0 {
|
|
|
|
return nil, notFound("Label").WithName(name)
|
|
|
|
}
|
|
|
|
if len(specs) > 1 {
|
2018-05-08 01:54:29 +00:00
|
|
|
return nil, errors.Errorf("expected 1 label row, got %d", len(specs))
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:12:32 +00:00
|
|
|
spec := specs[0]
|
2021-06-06 22:07:29 +00:00
|
|
|
if spec.LabelType != fleet.LabelTypeBuiltIn &&
|
|
|
|
spec.LabelMembershipType == fleet.LabelMembershipTypeManual {
|
2021-09-14 14:44:02 +00:00
|
|
|
err := d.getLabelHostnames(ctx, spec)
|
2020-04-07 22:12:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return spec, nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func (d *Datastore) getLabelHostnames(ctx context.Context, label *fleet.LabelSpec) error {
|
2020-04-07 22:12:32 +00:00
|
|
|
sql := `
|
2021-06-24 00:32:19 +00:00
|
|
|
SELECT hostname
|
2020-04-07 22:12:32 +00:00
|
|
|
FROM hosts
|
|
|
|
WHERE id IN
|
|
|
|
(
|
|
|
|
SELECT host_id
|
|
|
|
FROM label_membership
|
|
|
|
WHERE label_id = (SELECT id FROM labels WHERE name = ?)
|
|
|
|
)
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
err := sqlx.SelectContext(ctx, d.reader, &label.Hosts, sql, label.Name)
|
2020-04-07 22:12:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "get hostnames for label")
|
|
|
|
}
|
|
|
|
return nil
|
2018-05-08 01:54:29 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// NewLabel creates a new fleet.Label
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) NewLabel(ctx context.Context, label *fleet.Label, opts ...fleet.OptionalArg) (*fleet.Label, error) {
|
2020-10-22 17:51:26 +00:00
|
|
|
query := `
|
|
|
|
INSERT INTO labels (
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
query,
|
|
|
|
platform,
|
|
|
|
label_type,
|
|
|
|
label_membership_type
|
|
|
|
) VALUES ( ?, ?, ?, ?, ?, ?)
|
2018-06-18 17:09:08 +00:00
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
result, err := d.writer.ExecContext(
|
|
|
|
ctx,
|
2020-04-07 22:12:32 +00:00
|
|
|
query,
|
|
|
|
label.Name,
|
|
|
|
label.Description,
|
|
|
|
label.Query,
|
|
|
|
label.Platform,
|
|
|
|
label.LabelType,
|
|
|
|
label.LabelMembershipType,
|
|
|
|
)
|
2018-06-18 17:09:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "inserting label")
|
|
|
|
}
|
|
|
|
|
|
|
|
id, _ := result.LastInsertId()
|
|
|
|
label.ID = uint(id)
|
|
|
|
return label, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) SaveLabel(ctx context.Context, label *fleet.Label) (*fleet.Label, error) {
|
2018-06-18 17:09:08 +00:00
|
|
|
query := `
|
|
|
|
UPDATE labels SET
|
|
|
|
name = ?,
|
|
|
|
description = ?
|
|
|
|
WHERE id = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
_, err := d.writer.ExecContext(ctx, query, label.Name, label.Description, label.ID)
|
2018-06-18 17:09:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "saving label")
|
|
|
|
}
|
|
|
|
return label, nil
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// DeleteLabel deletes a fleet.Label
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) DeleteLabel(ctx context.Context, name string) error {
|
2021-09-14 14:44:02 +00:00
|
|
|
return d.deleteEntityByName(ctx, "labels", name)
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// Label returns a fleet.Label identified by lid if one exists.
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) Label(ctx context.Context, lid uint) (*fleet.Label, error) {
|
2016-11-16 13:47:49 +00:00
|
|
|
sql := `
|
|
|
|
SELECT * FROM labels
|
2020-10-22 17:51:26 +00:00
|
|
|
WHERE id = ?
|
2016-11-16 13:47:49 +00:00
|
|
|
`
|
2021-06-06 22:07:29 +00:00
|
|
|
label := &fleet.Label{}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.GetContext(ctx, d.reader, label, sql, lid); err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting label")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return label, nil
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// ListLabels returns all labels limited or sorted by fleet.ListOptions.
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ListLabels(ctx context.Context, filter fleet.TeamFilter, opt fleet.ListOptions) ([]*fleet.Label, error) {
|
2021-06-04 01:53:43 +00:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
SELECT *,
|
|
|
|
(SELECT COUNT(1) FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id) WHERE label_id = l.id AND %s) AS host_count
|
|
|
|
FROM labels l
|
|
|
|
`, d.whereFilterHostsByTeams(filter, "h"),
|
|
|
|
)
|
|
|
|
|
2017-01-16 22:57:05 +00:00
|
|
|
query = appendListOptionsToSQL(query, opt)
|
2021-06-06 22:07:29 +00:00
|
|
|
labels := []*fleet.Label{}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, d.reader, &labels, query); err != nil {
|
2017-01-16 22:57:05 +00:00
|
|
|
// it's ok if no labels exist
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return labels, nil
|
|
|
|
}
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting labels")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return labels, nil
|
|
|
|
}
|
|
|
|
|
2021-08-13 16:22:09 +00:00
|
|
|
func platformForHost(host *fleet.Host) string {
|
|
|
|
if host.Platform != "rhel" {
|
|
|
|
return host.Platform
|
|
|
|
}
|
|
|
|
if strings.Contains(strings.ToLower(host.OSVersion), "centos") {
|
|
|
|
return "centos"
|
|
|
|
}
|
|
|
|
return host.Platform
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) LabelQueriesForHost(ctx context.Context, host *fleet.Host, cutoff time.Time) (map[string]string, error) {
|
2020-04-07 01:10:20 +00:00
|
|
|
var rows *sql.Rows
|
|
|
|
var err error
|
2021-08-13 16:22:09 +00:00
|
|
|
platform := platformForHost(host)
|
2021-06-24 00:32:19 +00:00
|
|
|
if host.LabelUpdatedAt.Before(cutoff) {
|
2020-04-07 01:10:20 +00:00
|
|
|
// Retrieve all labels (with matching platform) for this host
|
|
|
|
sql := `
|
|
|
|
SELECT id, query
|
|
|
|
FROM labels
|
2020-04-07 22:12:32 +00:00
|
|
|
WHERE platform = ? OR platform = ''
|
|
|
|
AND label_membership_type = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
rows, err = d.reader.QueryContext(ctx, sql, platform, fleet.LabelMembershipTypeDynamic)
|
2020-04-07 01:10:20 +00:00
|
|
|
} else {
|
|
|
|
// Retrieve all labels (with matching platform) iff there is a label
|
|
|
|
// that has been created since this host last reported label query
|
|
|
|
// executions
|
|
|
|
sql := `
|
|
|
|
SELECT id, query
|
|
|
|
FROM labels
|
|
|
|
WHERE ((SELECT max(created_at) FROM labels WHERE platform = ? OR platform = '') > ?)
|
2020-04-07 22:12:32 +00:00
|
|
|
AND (platform = ? OR platform = '')
|
|
|
|
AND label_membership_type = ?
|
|
|
|
`
|
2021-09-14 14:44:02 +00:00
|
|
|
rows, err = d.reader.QueryContext(
|
|
|
|
ctx,
|
2020-04-07 22:12:32 +00:00
|
|
|
sql,
|
2021-08-13 16:22:09 +00:00
|
|
|
platform,
|
2021-06-24 00:32:19 +00:00
|
|
|
host.LabelUpdatedAt,
|
2021-08-13 16:22:09 +00:00
|
|
|
platform,
|
2021-06-06 22:07:29 +00:00
|
|
|
fleet.LabelMembershipTypeDynamic,
|
2020-04-07 22:12:32 +00:00
|
|
|
)
|
2020-04-07 01:10:20 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil && err != sql.ErrNoRows {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting label queries for host")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer rows.Close()
|
|
|
|
results := map[string]string{}
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
var id, query string
|
|
|
|
|
|
|
|
if err = rows.Scan(&id, &query); err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "scanning label queries for host")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
results[id] = query
|
|
|
|
}
|
2021-08-23 20:33:41 +00:00
|
|
|
if err := rows.Err(); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "iterating over returned rows")
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) RecordLabelQueryExecutions(ctx context.Context, host *fleet.Host, results map[uint]*bool, updated time.Time) error {
|
2021-06-21 23:10:24 +00:00
|
|
|
// Sort the results to have generated SQL queries ordered to minimize
|
2021-08-24 20:24:52 +00:00
|
|
|
// deadlocks. See https://github.com/fleetdm/fleet/issues/1146.
|
2021-06-21 23:10:24 +00:00
|
|
|
orderedIDs := make([]uint, 0, len(results))
|
2021-08-24 17:35:03 +00:00
|
|
|
for labelID := range results {
|
2021-06-21 23:10:24 +00:00
|
|
|
orderedIDs = append(orderedIDs, labelID)
|
|
|
|
}
|
|
|
|
sort.Slice(orderedIDs, func(i, j int) bool { return orderedIDs[i] < orderedIDs[j] })
|
|
|
|
|
2020-04-07 01:10:20 +00:00
|
|
|
// Loop through results, collecting which labels we need to insert/update,
|
|
|
|
// and which we need to delete
|
2016-11-16 13:47:49 +00:00
|
|
|
vals := []interface{}{}
|
2020-04-07 01:10:20 +00:00
|
|
|
bindvars := []string{}
|
|
|
|
removes := []uint{}
|
2021-06-21 23:10:24 +00:00
|
|
|
for _, labelID := range orderedIDs {
|
|
|
|
matches := results[labelID]
|
2021-08-24 20:24:52 +00:00
|
|
|
if matches != nil && *matches {
|
2020-04-07 01:10:20 +00:00
|
|
|
// Add/update row
|
|
|
|
bindvars = append(bindvars, "(?,?,?)")
|
|
|
|
vals = append(vals, updated, labelID, host.ID)
|
|
|
|
} else {
|
|
|
|
// Delete row
|
|
|
|
removes = append(removes, labelID)
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-08 18:43:22 +00:00
|
|
|
if len(vals) > 0 || len(removes) > 0 {
|
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
|
|
|
// Complete inserts if necessary
|
|
|
|
if len(vals) > 0 {
|
|
|
|
sql := `
|
|
|
|
INSERT IGNORE INTO label_membership (updated_at, label_id, host_id) VALUES
|
|
|
|
`
|
|
|
|
sql += strings.Join(bindvars, ",") +
|
|
|
|
`
|
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
|
updated_at = VALUES(updated_at)
|
|
|
|
`
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
_, err := tx.ExecContext(ctx, sql, vals...)
|
2021-09-08 18:43:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "insert label query executions (%v)", vals)
|
|
|
|
}
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-09-08 18:43:22 +00:00
|
|
|
// Complete deletions if necessary
|
|
|
|
if len(removes) > 0 {
|
|
|
|
sql := `
|
2020-04-07 01:10:20 +00:00
|
|
|
DELETE FROM label_membership WHERE host_id = ? AND label_id IN (?)
|
|
|
|
`
|
2021-09-08 18:43:22 +00:00
|
|
|
query, args, err := sqlx.In(sql, host.ID, removes)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "IN for DELETE FROM label_membership")
|
|
|
|
}
|
|
|
|
query = tx.Rebind(query)
|
2021-09-14 14:44:02 +00:00
|
|
|
_, err = tx.ExecContext(ctx, query, args...)
|
2021-09-08 18:43:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "delete label query executions")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2020-04-07 01:10:20 +00:00
|
|
|
if err != nil {
|
2021-09-08 18:43:22 +00:00
|
|
|
return err
|
2020-04-07 01:10:20 +00:00
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// ListLabelsForHost returns a list of fleet.Label for a given host id.
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ListLabelsForHost(ctx context.Context, hid uint) ([]*fleet.Label, error) {
|
2016-11-16 13:47:49 +00:00
|
|
|
sqlStatement := `
|
2020-04-07 01:10:20 +00:00
|
|
|
SELECT labels.* from labels JOIN label_membership lm
|
|
|
|
WHERE lm.host_id = ?
|
|
|
|
AND lm.label_id = labels.id
|
2016-11-16 13:47:49 +00:00
|
|
|
`
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
labels := []*fleet.Label{}
|
2021-09-14 14:44:02 +00:00
|
|
|
err := sqlx.SelectContext(ctx, d.reader, &labels, sqlStatement, hid)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting host labels")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2017-01-16 22:57:05 +00:00
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return labels, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// ListHostsInLabel returns a list of fleet.Host that are associated
|
|
|
|
// with fleet.Label referened by Label ID
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ListHostsInLabel(ctx context.Context, filter fleet.TeamFilter, lid uint, opt fleet.HostListOptions) ([]*fleet.Host, error) {
|
2021-06-04 01:53:43 +00:00
|
|
|
sql := fmt.Sprintf(`
|
2021-06-30 16:28:45 +00:00
|
|
|
SELECT h.*, (SELECT name FROM teams t WHERE t.id = h.team_id) AS team_name
|
2021-06-04 01:53:43 +00:00
|
|
|
FROM label_membership lm
|
|
|
|
JOIN hosts h
|
|
|
|
ON lm.host_id = h.id
|
|
|
|
WHERE lm.label_id = ? AND %s
|
|
|
|
`, d.whereFilterHostsByTeams(filter, "h"),
|
|
|
|
)
|
|
|
|
|
2021-02-18 20:52:43 +00:00
|
|
|
params := []interface{}{lid}
|
|
|
|
|
2021-08-05 17:56:29 +00:00
|
|
|
sql, params = filterHostsByStatus(sql, opt, params)
|
2021-08-11 14:40:56 +00:00
|
|
|
sql, params = filterHostsByTeam(sql, opt, params)
|
2021-02-18 20:52:43 +00:00
|
|
|
sql, params = searchLike(sql, params, opt.MatchQuery, hostSearchColumns...)
|
|
|
|
|
|
|
|
sql = appendListOptionsToSQL(sql, opt.ListOptions)
|
2021-06-06 22:07:29 +00:00
|
|
|
hosts := []*fleet.Host{}
|
2021-09-14 14:44:02 +00:00
|
|
|
err := sqlx.SelectContext(ctx, d.reader, &hosts, sql, params...)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting label query executions")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
return hosts, nil
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) ListUniqueHostsInLabels(ctx context.Context, filter fleet.TeamFilter, labels []uint) ([]*fleet.Host, error) {
|
2016-11-16 23:12:59 +00:00
|
|
|
if len(labels) == 0 {
|
2021-06-06 22:07:29 +00:00
|
|
|
return []*fleet.Host{}, nil
|
2016-11-16 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 01:53:43 +00:00
|
|
|
sqlStatement := fmt.Sprintf(`
|
2021-06-30 16:28:45 +00:00
|
|
|
SELECT DISTINCT h.*, (SELECT name FROM teams t WHERE t.id = h.team_id) AS team_name
|
2021-06-04 01:53:43 +00:00
|
|
|
FROM label_membership lm
|
|
|
|
JOIN hosts h
|
|
|
|
ON lm.host_id = h.id
|
|
|
|
WHERE lm.label_id IN (?) AND %s
|
|
|
|
`, d.whereFilterHostsByTeams(filter, "h"),
|
|
|
|
)
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
query, args, err := sqlx.In(sqlStatement, labels)
|
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "building query listing unique hosts in labels")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 19:50:52 +00:00
|
|
|
query = d.reader.Rebind(query)
|
2021-06-06 22:07:29 +00:00
|
|
|
hosts := []*fleet.Host{}
|
2021-09-14 14:44:02 +00:00
|
|
|
err = sqlx.SelectContext(ctx, d.reader, &hosts, query, args...)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "listing unique hosts in labels")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return hosts, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func (d *Datastore) searchLabelsWithOmits(ctx context.Context, filter fleet.TeamFilter, query string, omit ...uint) ([]*fleet.Label, error) {
|
2019-07-03 17:47:43 +00:00
|
|
|
transformedQuery := transformQuery(query)
|
|
|
|
|
2021-05-25 04:34:08 +00:00
|
|
|
sqlStatement := fmt.Sprintf(`
|
|
|
|
SELECT *,
|
|
|
|
(SELECT COUNT(1)
|
|
|
|
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
|
|
|
WHERE label_id = l.id AND %s
|
|
|
|
) AS host_count
|
|
|
|
FROM labels l
|
|
|
|
WHERE (
|
|
|
|
MATCH(name) AGAINST(? IN BOOLEAN MODE)
|
|
|
|
)
|
|
|
|
AND id NOT IN (?)
|
|
|
|
ORDER BY label_type DESC, id ASC
|
|
|
|
`, d.whereFilterHostsByTeams(filter, "h"),
|
|
|
|
)
|
2016-12-15 02:27:22 +00:00
|
|
|
|
2019-07-03 17:47:43 +00:00
|
|
|
sql, args, err := sqlx.In(sqlStatement, transformedQuery, omit)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "building query for labels with omits")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 19:50:52 +00:00
|
|
|
sql = d.reader.Rebind(sql)
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
matches := []*fleet.Label{}
|
2021-09-14 14:44:02 +00:00
|
|
|
err = sqlx.SelectContext(ctx, d.reader, &matches, sql, args...)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting labels with omits")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
matches, err = d.addAllHostsLabelToList(ctx, filter, matches, omit...)
|
2017-02-24 00:41:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "adding all hosts label to matches")
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return matches, nil
|
|
|
|
}
|
|
|
|
|
2017-02-24 00:41:12 +00:00
|
|
|
// When we search labels, we always want to make sure that the All Hosts label
|
|
|
|
// is included in the results set. Sometimes it already is and we don't need to
|
|
|
|
// add it, sometimes it's not so we explicitly add it.
|
2021-09-14 14:44:02 +00:00
|
|
|
func (d *Datastore) addAllHostsLabelToList(ctx context.Context, filter fleet.TeamFilter, labels []*fleet.Label, omit ...uint) ([]*fleet.Label, error) {
|
2021-05-25 04:34:08 +00:00
|
|
|
sql := fmt.Sprintf(`
|
|
|
|
SELECT *,
|
|
|
|
(SELECT COUNT(1)
|
|
|
|
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
|
|
|
WHERE label_id = l.id AND %s
|
|
|
|
) AS host_count
|
|
|
|
FROM labels l
|
|
|
|
WHERE
|
|
|
|
label_type=?
|
|
|
|
AND name = 'All Hosts'
|
|
|
|
LIMIT 1
|
|
|
|
`, d.whereFilterHostsByTeams(filter, "h"),
|
|
|
|
)
|
2017-02-24 00:41:12 +00:00
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
var allHosts fleet.Label
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.GetContext(ctx, d.reader, &allHosts, sql, fleet.LabelTypeBuiltIn); err != nil {
|
2021-05-25 04:34:08 +00:00
|
|
|
return nil, errors.Wrap(err, "get all hosts label")
|
2017-02-24 00:41:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, omission := range omit {
|
|
|
|
if omission == allHosts.ID {
|
|
|
|
return labels, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, label := range labels {
|
|
|
|
if label.ID == allHosts.ID {
|
|
|
|
return labels, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-26 01:53:22 +00:00
|
|
|
return append(labels, &allHosts), nil
|
2017-02-24 00:41:12 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
func (d *Datastore) searchLabelsDefault(ctx context.Context, filter fleet.TeamFilter, omit ...uint) ([]*fleet.Label, error) {
|
2021-05-25 04:34:08 +00:00
|
|
|
sql := fmt.Sprintf(`
|
|
|
|
SELECT *,
|
|
|
|
(SELECT COUNT(1)
|
|
|
|
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
|
|
|
WHERE label_id = l.id AND %s
|
|
|
|
) AS host_count
|
|
|
|
FROM labels l
|
|
|
|
WHERE id NOT IN (?)
|
|
|
|
GROUP BY id
|
|
|
|
ORDER BY label_type DESC, id ASC
|
|
|
|
`, d.whereFilterHostsByTeams(filter, "h"),
|
|
|
|
)
|
2017-01-17 14:51:04 +00:00
|
|
|
|
|
|
|
var in interface{}
|
|
|
|
{
|
|
|
|
// use -1 if there are no values to omit.
|
2020-10-22 17:51:26 +00:00
|
|
|
// Avoids empty args error for `sqlx.In`
|
2017-01-17 14:51:04 +00:00
|
|
|
in = omit
|
|
|
|
if len(omit) == 0 {
|
|
|
|
in = -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
var labels []*fleet.Label
|
2021-05-25 04:34:08 +00:00
|
|
|
sql, args, err := sqlx.In(sql, in)
|
2017-01-17 14:51:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "searching default labels")
|
|
|
|
}
|
2021-09-01 19:50:52 +00:00
|
|
|
sql = d.reader.Rebind(sql)
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, d.reader, &labels, sql, args...); err != nil {
|
2017-01-17 14:51:04 +00:00
|
|
|
return nil, errors.Wrap(err, "searching default labels rebound")
|
|
|
|
}
|
2017-02-24 00:41:12 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
labels, err = d.addAllHostsLabelToList(ctx, filter, labels, omit...)
|
2017-02-24 00:41:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "getting all host label")
|
|
|
|
}
|
|
|
|
|
2017-01-17 14:51:04 +00:00
|
|
|
return labels, nil
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
// SearchLabels performs wildcard searches on fleet.Label name
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) SearchLabels(ctx context.Context, filter fleet.TeamFilter, query string, omit ...uint) ([]*fleet.Label, error) {
|
2019-07-03 17:47:43 +00:00
|
|
|
transformedQuery := transformQuery(query)
|
|
|
|
if !queryMinLength(transformedQuery) {
|
2021-09-14 14:44:02 +00:00
|
|
|
return d.searchLabelsDefault(ctx, filter, omit...)
|
2017-01-17 14:51:04 +00:00
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
if len(omit) > 0 {
|
2021-09-14 14:44:02 +00:00
|
|
|
return d.searchLabelsWithOmits(ctx, filter, query, omit...)
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2017-03-02 23:40:50 +00:00
|
|
|
// Ordering first by label_type ensures that built-in labels come
|
|
|
|
// first. We will probably need to make a custom ordering function here
|
|
|
|
// if additional label types are added. Ordering next by ID ensures
|
|
|
|
// that the order is always consistent.
|
2021-05-25 04:34:08 +00:00
|
|
|
sql := fmt.Sprintf(`
|
|
|
|
SELECT *,
|
|
|
|
(SELECT COUNT(1)
|
|
|
|
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
|
|
|
WHERE label_id = l.id AND %s
|
|
|
|
) AS host_count
|
|
|
|
FROM labels l
|
|
|
|
WHERE (
|
|
|
|
MATCH(name) AGAINST(? IN BOOLEAN MODE)
|
|
|
|
)
|
|
|
|
ORDER BY label_type DESC, id ASC
|
|
|
|
`, d.whereFilterHostsByTeams(filter, "h"),
|
|
|
|
)
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
matches := []*fleet.Label{}
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, d.reader, &matches, sql, transformedQuery); err != nil {
|
2017-01-13 18:35:25 +00:00
|
|
|
return nil, errors.Wrap(err, "selecting labels for search")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2017-02-24 00:41:12 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
matches, err := d.addAllHostsLabelToList(ctx, filter, matches, omit...)
|
2017-02-24 00:41:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "adding all hosts label to matches")
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return matches, nil
|
|
|
|
}
|
2018-05-17 22:54:34 +00:00
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (d *Datastore) LabelIDsByName(ctx context.Context, labels []string) ([]uint, error) {
|
2020-01-24 05:27:20 +00:00
|
|
|
if len(labels) == 0 {
|
|
|
|
return []uint{}, nil
|
|
|
|
}
|
|
|
|
|
2018-05-17 22:54:34 +00:00
|
|
|
sqlStatement := `
|
|
|
|
SELECT id FROM labels
|
|
|
|
WHERE name IN (?)
|
|
|
|
`
|
|
|
|
|
|
|
|
sql, args, err := sqlx.In(sqlStatement, labels)
|
|
|
|
if err != nil {
|
2020-01-24 05:27:20 +00:00
|
|
|
return nil, errors.Wrap(err, "building query to get label IDs")
|
2018-05-17 22:54:34 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 05:27:20 +00:00
|
|
|
var labelIDs []uint
|
2021-09-14 14:44:02 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, d.reader, &labelIDs, sql, args...); err != nil {
|
2020-01-24 05:27:20 +00:00
|
|
|
return nil, errors.Wrap(err, "get label IDs")
|
2018-05-17 22:54:34 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 05:27:20 +00:00
|
|
|
return labelIDs, nil
|
2018-05-17 22:54:34 +00:00
|
|
|
|
|
|
|
}
|