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-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-02-18 20:52:43 +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 ) 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 {
2021-11-15 14:11:38 +00:00
return ctxerr . New ( ctx , "tx in ApplyLabelSpecs is not a sqlx.PreparerContext" )
2021-09-14 14:44:02 +00:00
}
stmt , err := prepTx . PrepareContext ( ctx , sql )
2018-01-10 19:38:20 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , 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 == "" {
2021-11-15 14:11:38 +00:00
return ctxerr . New ( ctx , "label name must not be empty" )
2020-03-11 01:14:02 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "exec ApplyLabelSpecs insert" )
2020-03-11 01:14:02 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "get label ID" )
2020-04-07 22:12:32 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "clear membership for ID" )
2020-04-07 22:12:32 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "build membership IN statement" )
2020-04-07 22:12:32 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "execute membership INSERT" )
2020-04-07 22:12:32 +00:00
}
}
2020-03-11 01:14:02 +00:00
}
return nil
} )
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "get labels" )
2018-01-10 19:38:20 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "get label" )
2018-05-08 01:54:29 +00:00
}
2018-05-09 23:54:42 +00:00
if len ( specs ) == 0 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , notFound ( "Label" ) . WithName ( name ) )
2018-05-09 23:54:42 +00:00
}
if len ( specs ) > 1 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Errorf ( ctx , "expected 1 label row, got %d" , len ( specs ) )
2018-05-08 01:54:29 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "get hostnames for label" )
2020-04-07 22:12:32 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "inserting label" )
2018-06-18 17:09:08 +00:00
}
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 ) {
2021-09-24 18:56:55 +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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "saving label" )
2018-06-18 17:09:08 +00:00
}
2021-09-24 18:56:55 +00:00
return labelDB ( ctx , label . ID , d . writer )
2018-06-18 17:09:08 +00:00
}
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-20 17:47:06 +00:00
return d . deleteEntityByName ( ctx , labelsTable , 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 ) {
2021-09-24 18:56:55 +00:00
return labelDB ( ctx , lid , d . reader )
}
func labelDB ( ctx context . Context , lid uint , q sqlx . QueryerContext ) ( * fleet . Label , error ) {
2016-11-16 13:47:49 +00:00
sql := `
2021-11-01 18:13:16 +00:00
SELECT
2021-09-24 18:56:55 +00:00
l . * ,
( SELECT COUNT ( 1 ) FROM label_membership lm JOIN hosts h ON ( lm . host_id = h . id ) WHERE label_id = l . id ) AS host_count
FROM labels l
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-24 18:56:55 +00:00
if err := sqlx . GetContext ( ctx , q , label , sql , lid ) ; err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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
}
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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-21 17:21:44 +00:00
func ( d * Datastore ) LabelQueriesForHost ( ctx context . Context , host * fleet . Host ) ( 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-09-21 17:21:44 +00:00
query := ` SELECT id, query FROM labels WHERE platform = ? OR platform = '' AND label_membership_type = ? `
rows , err = d . reader . QueryContext ( ctx , query , platform , fleet . LabelMembershipTypeDynamic )
2020-04-07 01:10:20 +00:00
2016-11-16 13:47:49 +00:00
if err != nil && err != sql . ErrNoRows {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "iterating over returned rows" )
2021-08-23 20:33:41 +00:00
}
2016-11-16 13:47:49 +00:00
return results , nil
}
2021-11-08 14:42:37 +00:00
func ( d * Datastore ) RecordLabelQueryExecutions ( ctx context . Context , host * fleet . Host , results map [ uint ] * bool , updated time . Time , deferredSaveHost bool ) 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-10-01 21:27:57 +00:00
err := d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
// Complete inserts if necessary
if len ( vals ) > 0 {
sql := ` INSERT INTO label_membership (updated_at, label_id, host_id) VALUES `
sql += strings . Join ( bindvars , "," ) + ` ON DUPLICATE KEY UPDATE updated_at = VALUES(updated_at) `
_ , err := tx . ExecContext ( ctx , sql , vals ... )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrapf ( ctx , err , "insert label query executions (%v)" , vals )
2021-09-08 18:43:22 +00:00
}
2021-10-01 21:27:57 +00:00
}
2016-11-16 13:47:49 +00:00
2021-10-01 21:27:57 +00:00
// Complete deletions if necessary
if len ( removes ) > 0 {
sql := ` DELETE FROM label_membership WHERE host_id = ? AND label_id IN (?) `
query , args , err := sqlx . In ( sql , host . ID , removes )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "IN for DELETE FROM label_membership" )
2021-10-01 21:27:57 +00:00
}
query = tx . Rebind ( query )
_ , err = tx . ExecContext ( ctx , query , args ... )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "delete label query executions" )
2021-09-08 18:43:22 +00:00
}
2021-10-01 21:27:57 +00:00
}
2021-11-08 14:42:37 +00:00
// if we are deferring host updates, we return at this point and do the change outside of the tx
if deferredSaveHost {
return nil
}
2021-10-01 21:27:57 +00:00
_ , err := tx . ExecContext ( ctx , ` UPDATE hosts SET label_updated_at = ? WHERE id=? ` , host . LabelUpdatedAt , host . ID )
2020-04-07 01:10:20 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "updating hosts label updated at" )
2020-04-07 01:10:20 +00:00
}
2021-10-01 21:27:57 +00:00
return nil
} )
if err != nil {
return err
2016-11-16 13:47:49 +00:00
}
2021-11-08 14:42:37 +00:00
if deferredSaveHost {
errCh := make ( chan error , 1 )
defer close ( errCh )
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
case d . writeCh <- itemToWrite {
ctx : ctx ,
errCh : errCh ,
item : hostXUpdatedAt {
hostID : host . ID ,
updatedAt : updated ,
what : "label_updated_at" ,
} ,
} :
return <- errCh
}
}
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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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-10-07 11:25:35 +00:00
query := `
2021-12-01 12:05:23 +00:00
SELECT
h . * ,
COALESCE ( hst . seen_time , h . created_at ) as seen_time ,
( 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
2021-11-08 14:42:37 +00:00
JOIN hosts h ON ( lm . host_id = h . id )
LEFT JOIN host_seen_times hst ON ( h . id = hst . host_id )
2021-10-07 11:25:35 +00:00
WHERE lm . label_id = ?
`
2021-02-18 20:52:43 +00:00
2021-10-07 11:25:35 +00:00
query , params := d . applyHostLabelFilters ( filter , lid , query , opt )
2021-02-18 20:52:43 +00:00
2021-06-06 22:07:29 +00:00
hosts := [ ] * fleet . Host { }
2021-10-07 11:25:35 +00:00
err := sqlx . SelectContext ( ctx , d . reader , & hosts , query , params ... )
2016-11-16 13:47:49 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "selecting label query executions" )
2016-11-16 13:47:49 +00:00
}
return hosts , nil
}
2021-11-10 17:24:19 +00:00
// NOTE: the hosts table must be aliased to `h` in the query passed to this function.
2021-10-07 11:25:35 +00:00
func ( d * Datastore ) applyHostLabelFilters ( filter fleet . TeamFilter , lid uint , query string , opt fleet . HostListOptions ) ( string , [ ] interface { } ) {
params := [ ] interface { } { lid }
query = fmt . Sprintf ( ` %s AND %s ` , query , d . whereFilterHostsByTeams ( filter , "h" ) )
query , params = filterHostsByStatus ( query , opt , params )
query , params = filterHostsByTeam ( query , opt , params )
query , params = searchLike ( query , params , opt . MatchQuery , hostSearchColumns ... )
query = appendListOptionsToSQL ( query , opt . ListOptions )
return query , params
}
func ( d * Datastore ) CountHostsInLabel ( ctx context . Context , filter fleet . TeamFilter , lid uint , opt fleet . HostListOptions ) ( int , error ) {
2021-11-10 17:24:19 +00:00
query := ` SELECT count ( * ) FROM label_membership lm
2021-11-08 14:42:37 +00:00
JOIN hosts h ON ( lm . host_id = h . id )
LEFT JOIN host_seen_times hst ON ( h . id = hst . host_id )
WHERE lm . label_id = ? `
2021-10-07 11:25:35 +00:00
query , params := d . applyHostLabelFilters ( filter , lid , query , opt )
var count int
if err := sqlx . GetContext ( ctx , d . reader , & count , query , params ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return 0 , ctxerr . Wrap ( ctx , err , "count hosts" )
2021-10-07 11:25:35 +00:00
}
return count , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "adding all hosts label to matches" )
2017-02-24 00:41:12 +00:00
}
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-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "searching default labels" )
2017-01-17 14:51:04 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "searching default labels rebound" )
2017-01-17 14:51:04 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "getting all host label" )
2017-02-24 00:41:12 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "adding all hosts label to matches" )
2017-02-24 00:41:12 +00:00
}
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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , 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 {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "get label IDs" )
2018-05-17 22:54:34 +00:00
}
2020-01-24 05:27:20 +00:00
return labelIDs , nil
2021-09-21 14:48:20 +00:00
}
2018-05-17 22:54:34 +00:00
2021-09-21 14:48:20 +00:00
func ( d * Datastore ) CleanupOrphanLabelMembership ( ctx context . Context ) error {
_ , err := d . writer . ExecContext ( ctx , ` DELETE FROM label_membership where not exists (select 1 from labels where id=label_id) or not exists (select 1 from hosts where id=host_id) ` )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "cleaning orphan label_membership by label" )
2021-09-21 14:48:20 +00:00
}
return nil
2018-05-17 22:54:34 +00:00
}
2021-11-01 18:13:16 +00:00
// AsyncBatchInsertLabelMembership inserts into the label_membership table the
// batch of label_id + host_id tuples represented by the [2]uint array.
func ( d * Datastore ) AsyncBatchInsertLabelMembership ( ctx context . Context , batch [ ] [ 2 ] uint ) error {
// NOTE: this is tested via the server/service/async package tests.
sql := ` INSERT INTO label_membership (label_id, host_id) VALUES `
sql += strings . Repeat ( ` (?, ?), ` , len ( batch ) )
sql = strings . TrimSuffix ( sql , "," )
sql += ` ON DUPLICATE KEY UPDATE updated_at = VALUES(updated_at) `
vals := make ( [ ] interface { } , 0 , len ( batch ) * 2 )
for _ , tup := range batch {
vals = append ( vals , tup [ 0 ] , tup [ 1 ] )
}
return d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
_ , err := tx . ExecContext ( ctx , sql , vals ... )
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "insert into label_membership" )
2021-11-01 18:13:16 +00:00
} )
}
// AsyncBatchDeleteLabelMembership deletes from the label_membership table the
// batch of label_id + host_id tuples represented by the [2]uint array.
func ( d * Datastore ) AsyncBatchDeleteLabelMembership ( ctx context . Context , batch [ ] [ 2 ] uint ) error {
// NOTE: this is tested via the server/service/async package tests.
rest := strings . Repeat ( ` UNION ALL SELECT ?, ? ` , len ( batch ) - 1 )
sql := fmt . Sprintf ( `
DELETE
lm
FROM
label_membership lm
JOIN
( SELECT ? label_id , ? host_id % s ) del_list
ON
lm . label_id = del_list . label_id AND
lm . host_id = del_list . host_id ` , rest )
vals := make ( [ ] interface { } , 0 , len ( batch ) * 2 )
for _ , tup := range batch {
vals = append ( vals , tup [ 0 ] , tup [ 1 ] )
}
return d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
_ , err := tx . ExecContext ( ctx , sql , vals ... )
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "delete from label_membership" )
2021-11-01 18:13:16 +00:00
} )
}
// AsyncBatchUpdateLabelTimestamp updates the table the hosts' label_updated_at timestamp
// for the batch of host ids provided.
func ( d * Datastore ) AsyncBatchUpdateLabelTimestamp ( ctx context . Context , ids [ ] uint , ts time . Time ) error {
// NOTE: this is tested via the server/service/async package tests.
sql := `
UPDATE
hosts
SET
label_updated_at = ?
WHERE
id IN ( ? ) `
query , args , err := sqlx . In ( sql , ts , ids )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "building query to update hosts.label_updated_at" )
2021-11-01 18:13:16 +00:00
}
return d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
_ , err := tx . ExecContext ( ctx , query , args ... )
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "update hosts.label_updated_at" )
2021-11-01 18:13:16 +00:00
} )
}