fleetctl, API, copy updates around host identifiers (#20220)
## Addresses #19127  - Also replace all ocurrences of "comma separated" with "comma-separated" - [x] Changes file added for user-visible changes in `changes/` - [x] `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
co-authored by
Jacob Shandling
parent
812140a760
commit
ec11e3d1d0
@@ -0,0 +1,2 @@
|
||||
- Update `fleetctl query --hosts` to work with hostnames, host UUIDs, and/or hardware serial numbers.
|
||||
- Clarify various help and error texts around host identifiers.
|
||||
@@ -36,7 +36,7 @@ func transferCommand() *cli.Command {
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: hostsFlagName,
|
||||
Usage: "Comma separated hostnames to transfer",
|
||||
Usage: "Comma-separated hostnames to transfer",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: labelFlagName,
|
||||
|
||||
+7
-11
@@ -41,7 +41,7 @@ func mdmRunCommand() *cli.Command {
|
||||
debugFlag(),
|
||||
&cli.StringSliceFlag{
|
||||
Name: "hosts",
|
||||
Usage: "Hosts specified by hostname, serial number, uuid, osquery_host_id or node_key that you want to target.",
|
||||
Usage: "Comma-separated hosts to target. Hosts can be specified by hostname, UUID, or serial number.",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -122,13 +122,9 @@ func mdmRunCommand() *cli.Command {
|
||||
hostUUIDs = append(hostUUIDs, host.UUID)
|
||||
}
|
||||
|
||||
if len(hostUUIDs) == 0 {
|
||||
// all hosts were not found
|
||||
return errors.New("No hosts targeted. Make sure you provide a valid hostname, UUID, osquery host ID, or node key.")
|
||||
}
|
||||
if notFoundCount > 0 {
|
||||
// at least one was not found
|
||||
return errors.New("One or more targeted hosts don't exist. Make sure you provide a valid hostname, UUID, osquery host ID, or node key.")
|
||||
if len(hostUUIDs) == 0 || notFoundCount > 0 {
|
||||
// Either no hosts were targeted, or at least one targeted host was not found
|
||||
return errors.New(fleet.TargetedHostsDontExistErrMsg)
|
||||
}
|
||||
|
||||
result, err := client.RunMDMCommand(hostUUIDs, payload, mdmPlatform)
|
||||
@@ -171,7 +167,7 @@ func mdmLockCommand() *cli.Command {
|
||||
Usage: "Lock a host when it needs to be returned to your organization.",
|
||||
Flags: []cli.Flag{contextFlag(), debugFlag(), &cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "The host, specified by identifier, that you want to lock.",
|
||||
Usage: "The host, specified by hostname, UUID, or serial number.",
|
||||
Required: true,
|
||||
}},
|
||||
Action: func(c *cli.Context) error {
|
||||
@@ -210,7 +206,7 @@ func mdmUnlockCommand() *cli.Command {
|
||||
Usage: "Unlock a host when it needs to be returned to your organization.",
|
||||
Flags: []cli.Flag{contextFlag(), debugFlag(), &cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "The host, specified by identifier, that you want to unlock.",
|
||||
Usage: "The host, specified by hostname, UUID, or serial number.",
|
||||
Required: true,
|
||||
}},
|
||||
Action: func(c *cli.Context) error {
|
||||
@@ -312,7 +308,7 @@ func hostMdmActionSetup(c *cli.Context, hostIdent string, actionType string) (cl
|
||||
if err != nil {
|
||||
var nfe service.NotFoundErr
|
||||
if errors.As(err, &nfe) {
|
||||
return nil, nil, errors.New("The host doesn't exist. Please provide a valid host identifier.")
|
||||
return nil, nil, errors.New(fleet.HostNotFoundErrMsg)
|
||||
}
|
||||
|
||||
var sce kithttp.StatusCoder
|
||||
|
||||
@@ -291,7 +291,7 @@ func TestMDMRunCommand(t *testing.T) {
|
||||
{"macOS yaml payload", []string{"--hosts", "mac-enrolled", "--payload", yamlFilePath}, appCfgAllMDM, `The payload isn't valid XML`},
|
||||
{"win yaml payload", []string{"--hosts", "win-enrolled", "--payload", yamlFilePath}, appCfgAllMDM, `The payload isn't valid XML`},
|
||||
{"non-mdm-command plist payload", []string{"--hosts", "mac-enrolled", "--payload", mobileConfigFilePath}, appCfgAllMDM, `The payload isn't valid. Please provide a valid MDM command in the form of a plist-encoded XML file:`},
|
||||
{"single host not found", []string{"--hosts", "no-such-host", "--payload", appleCmdFilePath}, appCfgAllMDM, `No hosts targeted.`},
|
||||
{"single host not found", []string{"--hosts", "no-such-host", "--payload", appleCmdFilePath}, appCfgAllMDM, fleet.TargetedHostsDontExistErrMsg},
|
||||
{"unenrolled macOS host", []string{"--hosts", "mac-unenrolled", "--payload", appleCmdFilePath}, appCfgAllMDM, `Can't run the MDM command because one or more hosts have MDM turned off.`},
|
||||
{"unenrolled windows host", []string{"--hosts", "win-unenrolled", "--payload", winCmdFilePath}, appCfgAllMDM, `Can't run the MDM command because one or more hosts have MDM turned off.`},
|
||||
{"macOS non-fleet host", []string{"--hosts", "mac-non-fleet-enrolled", "--payload", appleCmdFilePath}, appCfgAllMDM, `Can't run the MDM command because one or more hosts have MDM turned off.`},
|
||||
@@ -319,7 +319,7 @@ func TestMDMRunCommand(t *testing.T) {
|
||||
{"non-Exec win file", []string{"--hosts", "win-enrolled", "--payload", nonExecWinCmdFilePath.Name()}, appCfgAllMDM, `You can run only <Exec> command type.`},
|
||||
{"empty win file", []string{"--hosts", "win-enrolled", "--payload", emptyWinCmdFilePath.Name()}, appCfgAllMDM, `You can run only a single <Exec> command.`},
|
||||
{"hosts with different platforms", []string{"--hosts", "win-enrolled,mac-enrolled", "--payload", winCmdFilePath}, appCfgAllMDM, `Command can't run on hosts with different platforms.`},
|
||||
{"all hosts not found", []string{"--hosts", "no-such-1,no-such-2,no-such-3", "--payload", winCmdFilePath}, appCfgAllMDM, `No hosts targeted.`},
|
||||
{"all hosts not found", []string{"--hosts", "no-such-1,no-such-2,no-such-3", "--payload", winCmdFilePath}, appCfgAllMDM, fleet.TargetedHostsDontExistErrMsg},
|
||||
{"one host not found", []string{"--hosts", "win-enrolled,no-such-2,win-enrolled-2", "--payload", winCmdFilePath}, appCfgAllMDM, `One or more targeted hosts don't exist.`},
|
||||
{"one windows host not enrolled", []string{"--hosts", "win-enrolled,win-unenrolled,win-enrolled-2", "--payload", winCmdFilePath}, appCfgAllMDM, `Can't run the MDM command because one or more hosts have MDM turned off.`},
|
||||
{"one macOS host not enrolled", []string{"--hosts", "mac-enrolled,mac-unenrolled,mac-enrolled-2", "--payload", appleCmdFilePath}, appCfgAllMDM, `Can't run the MDM command because one or more hosts have MDM turned off.`},
|
||||
@@ -350,7 +350,6 @@ func TestMDMRunCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMDMLockCommand(t *testing.T) {
|
||||
|
||||
macEnrolled := testhost{
|
||||
host: &fleet.Host{
|
||||
ID: 1,
|
||||
@@ -590,7 +589,7 @@ fleetctl mdm unlock --host=%s
|
||||
}{
|
||||
{appCfgAllMDM, "no flags", nil, `Required flag "host" not set`},
|
||||
{appCfgAllMDM, "host flag empty", []string{"--host", ""}, `No host targeted. Please provide --host.`},
|
||||
{appCfgAllMDM, "lock non-existent host", []string{"--host", "notfound"}, `The host doesn't exist. Please provide a valid host identifier.`},
|
||||
{appCfgAllMDM, "lock non-existent host", []string{"--host", "notfound"}, fleet.HostNotFoundErrMsg},
|
||||
{appCfgMacMDM, "valid windows but only macos mdm", []string{"--host", winEnrolled.host.UUID}, `Windows MDM isn't turned on.`},
|
||||
{appCfgWinMDM, "valid macos but only windows mdm", []string{"--host", macEnrolled.host.UUID}, `macOS MDM isn't turned on.`},
|
||||
{appCfgAllMDM, "valid windows", []string{"--host", winEnrolled.host.UUID}, ""},
|
||||
@@ -866,7 +865,7 @@ fleetctl get host %s
|
||||
}{
|
||||
{appCfgAllMDM, "no flags", nil, `Required flag "host" not set`},
|
||||
{appCfgAllMDM, "host flag empty", []string{"--host", ""}, `No host targeted. Please provide --host.`},
|
||||
{appCfgAllMDM, "unlock non-existent host", []string{"--host", "notfound"}, `The host doesn't exist. Please provide a valid host identifier.`},
|
||||
{appCfgAllMDM, "unlock non-existent host", []string{"--host", "notfound"}, fleet.HostNotFoundErrMsg},
|
||||
{appCfgMacMDM, "valid windows but only macos mdm", []string{"--host", winEnrolled.host.UUID}, `Windows MDM isn't turned on.`},
|
||||
{appCfgAllMDM, "valid windows", []string{"--host", winEnrolled.host.UUID}, ""},
|
||||
{appCfgAllMDM, "valid macos", []string{"--host", macEnrolled.host.UUID}, ""},
|
||||
@@ -1225,7 +1224,7 @@ func TestMDMWipeCommand(t *testing.T) {
|
||||
}{
|
||||
{appCfgAllMDM, "no flags", nil, `Required flag "host" not set`},
|
||||
{appCfgAllMDM, "host flag empty", []string{"--host", ""}, `No host targeted. Please provide --host.`},
|
||||
{appCfgAllMDM, "wipe non-existent host", []string{"--host", "notfound"}, `The host doesn't exist. Please provide a valid host identifier.`},
|
||||
{appCfgAllMDM, "wipe non-existent host", []string{"--host", "notfound"}, fleet.HostNotFoundErrMsg},
|
||||
{appCfgMacMDM, "valid windows but only macos mdm", []string{"--host", winEnrolled.host.UUID}, `Windows MDM isn't turned on.`},
|
||||
{appCfgAllMDM, "valid windows", []string{"--host", winEnrolled.host.UUID}, ""},
|
||||
{appCfgAllMDM, "valid macos", []string{"--host", macEnrolled.host.UUID}, ""},
|
||||
|
||||
+10
-6
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -28,14 +29,14 @@ func queryCommand() *cli.Command {
|
||||
EnvVars: []string{"HOSTS"},
|
||||
Value: "",
|
||||
Destination: &flHosts,
|
||||
Usage: "Comma separated hostnames to target",
|
||||
Usage: "Comma-separated hosts to target. Hosts can be specified by hostname, UUID, or serial number.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "labels",
|
||||
EnvVars: []string{"LABELS"},
|
||||
Value: "",
|
||||
Destination: &flLabels,
|
||||
Usage: "Comma separated label names to target",
|
||||
Usage: "Comma-separated label names to target",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "quiet",
|
||||
@@ -84,7 +85,7 @@ func queryCommand() *cli.Command {
|
||||
debugFlag(),
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
fleet, err := clientFromCLI(c)
|
||||
client, err := clientFromCLI(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,7 +104,7 @@ func queryCommand() *cli.Command {
|
||||
if tid := c.Uint(teamFlagName); tid != 0 {
|
||||
teamID = &tid
|
||||
}
|
||||
queries, err := fleet.GetQueries(teamID, &flQueryName)
|
||||
queries, err := client.GetQueries(teamID, &flQueryName)
|
||||
if err != nil || len(queries) == 0 {
|
||||
return fmt.Errorf("Query '%s' not found", flQueryName)
|
||||
}
|
||||
@@ -131,11 +132,14 @@ func queryCommand() *cli.Command {
|
||||
output = newJsonWriter(c.App.Writer)
|
||||
}
|
||||
|
||||
hosts := strings.Split(flHosts, ",")
|
||||
hostIdentifiers := strings.Split(flHosts, ",")
|
||||
labels := strings.Split(flLabels, ",")
|
||||
|
||||
res, err := fleet.LiveQuery(flQuery, queryID, labels, hosts)
|
||||
res, err := client.LiveQuery(flQuery, queryID, labels, hostIdentifiers)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no hosts targeted") {
|
||||
return errors.New(fleet.NoHostsTargetedErrMsg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestSavedLiveQuery(t *testing.T) {
|
||||
Saved: true,
|
||||
}
|
||||
|
||||
ds.HostIDsByNameFunc = func(ctx context.Context, filter fleet.TeamFilter, hostnames []string) ([]uint, error) {
|
||||
ds.HostIDsByIdentifierFunc = func(ctx context.Context, filter fleet.TeamFilter, hostIdentifiers []string) ([]uint, error) {
|
||||
return []uint{1234}, nil
|
||||
}
|
||||
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
|
||||
@@ -149,6 +149,11 @@ func TestSavedLiveQuery(t *testing.T) {
|
||||
))
|
||||
}()
|
||||
|
||||
// errors before requesting live query
|
||||
_, err = runAppNoChecks([]string{"query", "--hosts", "", "--query-name", queryName})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "No hosts or labels targeted")
|
||||
|
||||
expected := `{"host":"somehostname","rows":[{"bing":"fds","host_display_name":"somehostname","host_hostname":"somehostname"}]}
|
||||
`
|
||||
// Note: runAppForTest never closes the WebSocket connection and does not exit,
|
||||
@@ -197,7 +202,7 @@ func TestAdHocLiveQuery(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
ds.HostIDsByNameFunc = func(ctx context.Context, filter fleet.TeamFilter, hostnames []string) ([]uint, error) {
|
||||
ds.HostIDsByIdentifierFunc = func(ctx context.Context, filter fleet.TeamFilter, hostIdentifiers []string) ([]uint, error) {
|
||||
return []uint{1234}, nil
|
||||
}
|
||||
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
|
||||
|
||||
@@ -37,7 +37,7 @@ func runScriptCommand() *cli.Command {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "A host, specified by hostname, serial number, UUID, osquery host ID, or node key.",
|
||||
Usage: "The host, specified by hostname, UUID, or serial number.",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -108,7 +108,7 @@ func runScriptCommand() *cli.Command {
|
||||
if err != nil {
|
||||
var nfe service.NotFoundErr
|
||||
if errors.As(err, &nfe) {
|
||||
return errors.New(fleet.RunScriptHostNotFoundErrMsg)
|
||||
return errors.New(fleet.HostNotFoundErrMsg)
|
||||
}
|
||||
var sce fleet.ErrWithStatusCode
|
||||
if errors.As(err, &sce) {
|
||||
|
||||
@@ -102,7 +102,7 @@ hello world
|
||||
{
|
||||
name: "host not found",
|
||||
scriptPath: generateValidPath,
|
||||
expectErrMsg: fleet.RunScriptHostNotFoundErrMsg,
|
||||
expectErrMsg: fleet.HostNotFoundErrMsg,
|
||||
expectNotFound: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2299,7 +2299,7 @@ func main() {
|
||||
munkiIssueCount = flag.Int("munki_issue_count", 10, "Number of munki issues reported by hosts identified to have munki issues")
|
||||
// E.g. when running with `-host_count=10`, you can set host count for each template the following way:
|
||||
// `-os_templates=windows_11.tmpl:3,macos_14.1.2.tmpl:4,ubuntu_22.04.tmpl:3`
|
||||
osTemplates = flag.String("os_templates", "macos_14.1.2", fmt.Sprintf("Comma separated list of host OS templates to use and optionally their host count separated by ':' (any of %v, with or without the .tmpl extension)", allowedTemplateNames))
|
||||
osTemplates = flag.String("os_templates", "macos_14.1.2", fmt.Sprintf("Comma-separated list of host OS templates to use and optionally their host count separated by ':' (any of %v, with or without the .tmpl extension)", allowedTemplateNames))
|
||||
emptySerialProb = flag.Float64("empty_serial_prob", 0.1, "Probability of a host having no serial number [0, 1]")
|
||||
|
||||
mdmProb = flag.Float64("mdm_prob", 0.0, "Probability of a host enrolling via Fleet MDM (applies for macOS and Windows hosts, implies orbit enrollment on Windows) [0, 1]")
|
||||
|
||||
@@ -17,7 +17,7 @@ const formatFieldForCSV = (value: any): string => {
|
||||
}
|
||||
|
||||
// Wrap the value in double quotes to enclose any value that may
|
||||
// have a, or a " in it to distinguish them from a comma separated delimiter
|
||||
// have a, or a " in it to distinguish them from a comma-separated delimiter
|
||||
return `"${value}"`;
|
||||
};
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ module Crypttab =
|
||||
|
||||
(************************************************************************
|
||||
* View: comma_sep_list
|
||||
* A comma separated list of options (opt=value or opt)
|
||||
* A comma-separated list of options (opt=value or opt)
|
||||
*************************************************************************)
|
||||
let comma_sep_list (l:string) =
|
||||
let value = [ label "value" . Util.del_str "=" . store optval ] in
|
||||
|
||||
@@ -47,7 +47,7 @@ let integer = Rx.integer
|
||||
(* View: member *)
|
||||
let member = [ label "member" . store word ]
|
||||
(* View: member_list
|
||||
the member list is a comma separated list of
|
||||
the member list is a comma-separated list of
|
||||
users allowed to chgrp to the group without
|
||||
being prompted for the group's password *)
|
||||
let member_list = Build.opt_list member comma
|
||||
@@ -55,7 +55,7 @@ let member_list = Build.opt_list member comma
|
||||
(* View: admin *)
|
||||
let admin = [ label "admin" . store word ]
|
||||
(* View: admin_list
|
||||
the admin_list is a comma separated list of
|
||||
the admin_list is a comma-separated list of
|
||||
users allowed to change the group's password
|
||||
and the member_list *)
|
||||
let admin_list = Build.opt_list admin comma
|
||||
|
||||
@@ -8,7 +8,7 @@ module Opendkim =
|
||||
|
||||
(*
|
||||
The Dataset spec is so broad as to encompass any string (particularly the
|
||||
degenerate 'single literal string' case of a comma separated list with
|
||||
degenerate 'single literal string' case of a comma-separated list with
|
||||
only one item). So treat them as 'String' types, and it's up to the user to
|
||||
format them correctly. Given that many of the variants include file paths
|
||||
etc, it's impossible to validate for 'correctness' anyway
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// parseOptions parses the stdout returned from falconctl's displayed options. As far as we know, output is a single
|
||||
// line, comma separated. We parse multiple lines, but assume data does not space that. Eg: linebreaks and commas
|
||||
// line, comma-separated. We parse multiple lines, but assume data does not space that. Eg: linebreaks and commas
|
||||
// treated as seperators.
|
||||
func parseOptions(reader io.Reader) (any, error) {
|
||||
results := make(map[string]interface{})
|
||||
|
||||
@@ -207,7 +207,7 @@ func getCmdResponseData(outputCmd string) (string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// results will be appended in a comma separated list
|
||||
// results will be appended in a comma-separated list
|
||||
if len(element.Item) > 0 {
|
||||
|
||||
// extracting the data from the result
|
||||
|
||||
@@ -2547,25 +2547,30 @@ func (ds *Datastore) SearchHosts(ctx context.Context, filter fleet.TeamFilter, m
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) HostIDsByName(ctx context.Context, filter fleet.TeamFilter, hostnames []string) ([]uint, error) {
|
||||
if len(hostnames) == 0 {
|
||||
func (ds *Datastore) HostIDsByIdentifier(ctx context.Context, filter fleet.TeamFilter, hostIdentifiers []string) ([]uint, error) {
|
||||
if len(hostIdentifiers) == 0 {
|
||||
return []uint{}, nil
|
||||
}
|
||||
|
||||
sqlStatement := fmt.Sprintf(`
|
||||
SELECT id FROM hosts
|
||||
WHERE hostname IN (?) AND %s
|
||||
SELECT
|
||||
DISTINCT id FROM hosts
|
||||
WHERE
|
||||
(hostname IN (?)
|
||||
OR uuid IN (?)
|
||||
OR hardware_serial IN (?))
|
||||
AND %s
|
||||
`, ds.whereFilterHostsByTeams(filter, "hosts"),
|
||||
)
|
||||
|
||||
sql, args, err := sqlx.In(sqlStatement, hostnames)
|
||||
sql, args, err := sqlx.In(sqlStatement, hostIdentifiers, hostIdentifiers, hostIdentifiers)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "building query to get host IDs")
|
||||
return nil, ctxerr.Wrap(ctx, err, "building query to get host IDs by identifier")
|
||||
}
|
||||
|
||||
var hostIDs []uint
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &hostIDs, sql, args...); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get host IDs")
|
||||
return nil, ctxerr.Wrap(ctx, err, "get host IDs by identifier")
|
||||
}
|
||||
|
||||
return hostIDs, nil
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestHosts(t *testing.T) {
|
||||
{"MarkSeen", testHostsMarkSeen},
|
||||
{"MarkSeenMany", testHostsMarkSeenMany},
|
||||
{"CleanupIncoming", testHostsCleanupIncoming},
|
||||
{"IDsByName", testHostsIDsByName},
|
||||
{"IDsByIdentifier", testHostIDsByIdentifier},
|
||||
{"Additional", testHostsAdditional},
|
||||
{"ByIdentifier", testHostsByIdentifier},
|
||||
{"HostLiteByIdentifierAndID", testHostLiteByIdentifierAndID},
|
||||
@@ -2273,7 +2273,7 @@ func testHostsCleanupIncoming(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testHostsIDsByName(t *testing.T, ds *Datastore) {
|
||||
func testHostIDsByIdentifier(t *testing.T, ds *Datastore) {
|
||||
hosts := make([]*fleet.Host, 10)
|
||||
for i := range hosts {
|
||||
h, err := ds.NewHost(context.Background(), &fleet.Host{
|
||||
@@ -2281,9 +2281,10 @@ func testHostsIDsByName(t *testing.T, ds *Datastore) {
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
OsqueryHostID: ptr.String(fmt.Sprintf("host%d", i)),
|
||||
NodeKey: ptr.String(fmt.Sprintf("%d", i)),
|
||||
UUID: fmt.Sprintf("%d", i),
|
||||
OsqueryHostID: ptr.String(fmt.Sprintf("osq.host%d", i)),
|
||||
NodeKey: ptr.String(fmt.Sprintf("nk.%d", i)),
|
||||
UUID: fmt.Sprintf("uuid.%d", i),
|
||||
HardwareSerial: fmt.Sprintf("hws.%d", i),
|
||||
Hostname: fmt.Sprintf("foo.%d.local", i),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -2295,34 +2296,47 @@ func testHostsIDsByName(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{hosts[0].ID}))
|
||||
|
||||
filter := fleet.TeamFilter{User: test.UserAdmin}
|
||||
hostsByName, err := ds.HostIDsByName(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
hostsByIdentifier, err := ds.HostIDsByIdentifier(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
require.NoError(t, err)
|
||||
sort.Slice(hostsByName, func(i, j int) bool { return hostsByName[i] < hostsByName[j] })
|
||||
assert.Equal(t, hostsByName, []uint{2, 3, 6})
|
||||
sort.Slice(hostsByIdentifier, func(i, j int) bool { return hostsByIdentifier[i] < hostsByIdentifier[j] })
|
||||
assert.Equal(t, hostsByIdentifier, []uint{2, 3, 6})
|
||||
|
||||
// by UUID
|
||||
hostsByIdentifier, err = ds.HostIDsByIdentifier(context.Background(), filter, []string{"uuid.0", "uuid.4"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostsByIdentifier, 2)
|
||||
assert.Equal(t, hostsByIdentifier[0], hosts[0].ID)
|
||||
assert.Equal(t, hostsByIdentifier[1], hosts[4].ID)
|
||||
|
||||
// by HardwareSerial
|
||||
hostsByIdentifier, err = ds.HostIDsByIdentifier(context.Background(), filter, []string{"hws.2"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostsByIdentifier, 1)
|
||||
assert.Equal(t, hostsByIdentifier[0], hosts[2].ID)
|
||||
|
||||
userObs := &fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)}
|
||||
filter = fleet.TeamFilter{User: userObs}
|
||||
|
||||
hostsByName, err = ds.HostIDsByName(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
hostsByIdentifier, err = ds.HostIDsByIdentifier(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, hostsByName, 0)
|
||||
assert.Len(t, hostsByIdentifier, 0)
|
||||
|
||||
filter.IncludeObserver = true
|
||||
hostsByName, err = ds.HostIDsByName(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
hostsByIdentifier, err = ds.HostIDsByIdentifier(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, hostsByName, 3)
|
||||
assert.Len(t, hostsByIdentifier, 3)
|
||||
|
||||
userTeam1 := &fleet.User{Teams: []fleet.UserTeam{{Team: *team1, Role: fleet.RoleAdmin}}}
|
||||
filter = fleet.TeamFilter{User: userTeam1}
|
||||
|
||||
hostsByName, err = ds.HostIDsByName(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
hostsByIdentifier, err = ds.HostIDsByIdentifier(context.Background(), filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, hostsByName, 0)
|
||||
assert.Len(t, hostsByIdentifier, 0)
|
||||
|
||||
hostsByName, err = ds.HostIDsByName(context.Background(), filter, []string{"foo.0.local", "foo.1.local", "foo.5.local"})
|
||||
hostsByIdentifier, err = ds.HostIDsByIdentifier(context.Background(), filter, []string{"foo.0.local", "foo.1.local", "foo.5.local"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostsByName, 1)
|
||||
assert.Equal(t, hostsByName[0], hosts[0].ID)
|
||||
require.Len(t, hostsByIdentifier, 1)
|
||||
assert.Equal(t, hostsByIdentifier[0], hosts[0].ID)
|
||||
}
|
||||
|
||||
func testLoadHostByNodeKeyLoadsDisk(t *testing.T, ds *Datastore) {
|
||||
|
||||
+1
-1
@@ -995,7 +995,7 @@ type ListOptions struct {
|
||||
// How many results per page (must be positive integer, 0 indicates
|
||||
// unlimited)
|
||||
PerPage uint `query:"per_page,optional"`
|
||||
// Key to use for ordering. Can be a comma separated set of items, eg: host_count,id
|
||||
// Key to use for ordering. Can be a comma-separated set of items, eg: host_count,id
|
||||
OrderKey string `query:"order_key,optional"`
|
||||
// Direction of ordering
|
||||
OrderDirection OrderDirection `query:"order_direction,optional"`
|
||||
|
||||
@@ -15,7 +15,7 @@ type CapabilityMap map[Capability]struct{}
|
||||
// mu is used to allow for safe access to the capability map.
|
||||
var mu sync.Mutex
|
||||
|
||||
// PopulateFromString populates the CapabilityMap from a comma separated string.
|
||||
// PopulateFromString populates the CapabilityMap from a comma-separated string.
|
||||
// Example: "foo,bar,baz" => {"foo": struct{}, "bar": struct{}, "baz": struct{}}
|
||||
func (c *CapabilityMap) PopulateFromString(s string) {
|
||||
mu.Lock()
|
||||
@@ -31,7 +31,7 @@ func (c *CapabilityMap) PopulateFromString(s string) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a comma separated string with the capabilities in the map.
|
||||
// String returns a comma-separated string with the capabilities in the map.
|
||||
// Example: {"foo": struct{}, "bar": struct{}, "baz": struct{}} => "foo,bar,baz"
|
||||
func (c *CapabilityMap) String() string {
|
||||
mu.Lock()
|
||||
|
||||
@@ -261,8 +261,8 @@ type Datastore interface {
|
||||
CleanupIncomingHosts(ctx context.Context, now time.Time) ([]uint, error)
|
||||
// GenerateHostStatusStatistics retrieves the count of online, offline, MIA and new hosts.
|
||||
GenerateHostStatusStatistics(ctx context.Context, filter TeamFilter, now time.Time, platform *string, lowDiskSpace *int) (*HostSummary, error)
|
||||
// HostIDsByName Retrieve the IDs associated with the given hostnames
|
||||
HostIDsByName(ctx context.Context, filter TeamFilter, hostnames []string) ([]uint, error)
|
||||
// HostIDsByIdentifier retrieves the IDs associated with the given hostnames, UUIDs, or hardware serials.
|
||||
HostIDsByIdentifier(ctx context.Context, filter TeamFilter, hostnames []string) ([]uint, error)
|
||||
|
||||
// HostIDsByOSID retrieves the IDs of all host for the given OS ID
|
||||
HostIDsByOSID(ctx context.Context, osID uint, offset int, limit int) ([]uint, error)
|
||||
|
||||
@@ -536,10 +536,14 @@ func (e OrbitError) Error() string {
|
||||
|
||||
// Message that may surfaced by the server or the fleetctl client.
|
||||
const (
|
||||
// Hosts, general
|
||||
HostNotFoundErrMsg = "Host doesn't exist. Make sure you provide a valid hostname, UUID, or serial number. Learn more about host identifiers: https://fleetdm.com/learn-more-about/host-identifiers"
|
||||
NoHostsTargetedErrMsg = "No hosts targeted. Make sure you provide a valid hostname, UUID, or serial number. Learn more about host identifiers: https://fleetdm.com/learn-more-about/host-identifiers"
|
||||
TargetedHostsDontExistErrMsg = "One or more targeted hosts don't exist. Make sure you provide a valid hostname, UUID, or serial number. Learn more about host identifiers: https://fleetdm.com/learn-more-about/host-identifiers"
|
||||
|
||||
// Scripts
|
||||
RunScriptInvalidTypeErrMsg = "File type not supported. Only .sh (Bash) and .ps1 (PowerShell) file types are allowed."
|
||||
RunScriptHostOfflineErrMsg = "Script can't run on offline host."
|
||||
RunScriptHostNotFoundErrMsg = "Host doesn't exist. Make sure you provide a valid hostname, UUID, osquery host ID, or node key."
|
||||
RunScriptForbiddenErrMsg = "You don't have the right permissions in Fleet to run the script."
|
||||
RunScriptAlreadyRunningErrMsg = "A script is already running on this host. Please wait about 5 minutes to let it finish."
|
||||
RunScriptHostTimeoutErrMsg = "Fleet didn't hear back from the host in under 5 minutes (timeout for live scripts). Fleet doesn't know if the script ran because it didn't receive the result. Please try again."
|
||||
@@ -554,6 +558,7 @@ const (
|
||||
|
||||
// End user authentication
|
||||
EndUserAuthDEPWebURLConfiguredErrMsg = `End user authentication can't be configured when the configured automatic enrollment (DEP) profile specifies a configuration_web_url.` // #nosec G101
|
||||
|
||||
)
|
||||
|
||||
// ConflictError is used to indicate a conflict, such as a UUID conflict in the DB.
|
||||
|
||||
@@ -294,9 +294,9 @@ type Service interface {
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// CampaignService defines the distributed query campaign related service methods
|
||||
|
||||
// NewDistributedQueryCampaignByNames creates a new distributed query campaign with the provided query (or the query
|
||||
// referenced by ID) and host/label targets (specified by name).
|
||||
NewDistributedQueryCampaignByNames(
|
||||
// NewDistributedQueryCampaignByIdentifiers creates a new distributed query campaign with the provided query (or the query
|
||||
// referenced by ID) and host/label targets (specified by hostname, UUID, or hardware serial).
|
||||
NewDistributedQueryCampaignByIdentifiers(
|
||||
ctx context.Context, queryString string, queryID *uint, hosts []string, labels []string,
|
||||
) (*DistributedQueryCampaign, error)
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ type CleanupIncomingHostsFunc func(ctx context.Context, now time.Time) ([]uint,
|
||||
|
||||
type GenerateHostStatusStatisticsFunc func(ctx context.Context, filter fleet.TeamFilter, now time.Time, platform *string, lowDiskSpace *int) (*fleet.HostSummary, error)
|
||||
|
||||
type HostIDsByNameFunc func(ctx context.Context, filter fleet.TeamFilter, hostnames []string) ([]uint, error)
|
||||
type HostIDsByIdentifierFunc func(ctx context.Context, filter fleet.TeamFilter, hostnames []string) ([]uint, error)
|
||||
|
||||
type HostIDsByOSIDFunc func(ctx context.Context, osID uint, offset int, limit int) ([]uint, error)
|
||||
|
||||
@@ -1244,8 +1244,8 @@ type DataStore struct {
|
||||
GenerateHostStatusStatisticsFunc GenerateHostStatusStatisticsFunc
|
||||
GenerateHostStatusStatisticsFuncInvoked bool
|
||||
|
||||
HostIDsByNameFunc HostIDsByNameFunc
|
||||
HostIDsByNameFuncInvoked bool
|
||||
HostIDsByIdentifierFunc HostIDsByIdentifierFunc
|
||||
HostIDsByIdentifierFuncInvoked bool
|
||||
|
||||
HostIDsByOSIDFunc HostIDsByOSIDFunc
|
||||
HostIDsByOSIDFuncInvoked bool
|
||||
@@ -3040,11 +3040,11 @@ func (s *DataStore) GenerateHostStatusStatistics(ctx context.Context, filter fle
|
||||
return s.GenerateHostStatusStatisticsFunc(ctx, filter, now, platform, lowDiskSpace)
|
||||
}
|
||||
|
||||
func (s *DataStore) HostIDsByName(ctx context.Context, filter fleet.TeamFilter, hostnames []string) ([]uint, error) {
|
||||
func (s *DataStore) HostIDsByIdentifier(ctx context.Context, filter fleet.TeamFilter, hostIdentifiers []string) ([]uint, error) {
|
||||
s.mu.Lock()
|
||||
s.HostIDsByNameFuncInvoked = true
|
||||
s.HostIDsByIdentifierFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.HostIDsByNameFunc(ctx, filter, hostnames)
|
||||
return s.HostIDsByIdentifierFunc(ctx, filter, hostIdentifiers)
|
||||
}
|
||||
|
||||
func (s *DataStore) HostIDsByOSID(ctx context.Context, osID uint, offset int, limit int) ([]uint, error) {
|
||||
|
||||
+12
-11
@@ -174,34 +174,35 @@ func (svc *Service) NewDistributedQueryCampaign(ctx context.Context, queryString
|
||||
// Create Distributed Query Campaign By Names
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type createDistributedQueryCampaignByNamesRequest struct {
|
||||
QuerySQL string `json:"query"`
|
||||
QueryID *uint `json:"query_id"`
|
||||
Selected distributedQueryCampaignTargetsByNames `json:"selected"`
|
||||
type createDistributedQueryCampaignByIdentifierRequest struct {
|
||||
QuerySQL string `json:"query"`
|
||||
QueryID *uint `json:"query_id"`
|
||||
Selected distributedQueryCampaignTargetsByIdentifiers `json:"selected"`
|
||||
}
|
||||
|
||||
type distributedQueryCampaignTargetsByNames struct {
|
||||
type distributedQueryCampaignTargetsByIdentifiers struct {
|
||||
Labels []string `json:"labels"`
|
||||
Hosts []string `json:"hosts"`
|
||||
// list of hostnames, UUIDs, and/or hardware serials
|
||||
Hosts []string `json:"hosts"`
|
||||
}
|
||||
|
||||
func createDistributedQueryCampaignByNamesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||
req := request.(*createDistributedQueryCampaignByNamesRequest)
|
||||
campaign, err := svc.NewDistributedQueryCampaignByNames(ctx, req.QuerySQL, req.QueryID, req.Selected.Hosts, req.Selected.Labels)
|
||||
func createDistributedQueryCampaignByIdentifierEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||
req := request.(*createDistributedQueryCampaignByIdentifierRequest)
|
||||
campaign, err := svc.NewDistributedQueryCampaignByIdentifiers(ctx, req.QuerySQL, req.QueryID, req.Selected.Hosts, req.Selected.Labels)
|
||||
if err != nil {
|
||||
return createDistributedQueryCampaignResponse{Err: err}, nil
|
||||
}
|
||||
return createDistributedQueryCampaignResponse{Campaign: campaign}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) NewDistributedQueryCampaignByNames(ctx context.Context, queryString string, queryID *uint, hosts []string, labels []string) (*fleet.DistributedQueryCampaign, error) {
|
||||
func (svc *Service) NewDistributedQueryCampaignByIdentifiers(ctx context.Context, queryString string, queryID *uint, hostIdentifiers []string, labels []string) (*fleet.DistributedQueryCampaign, error) {
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, fleet.ErrNoContext
|
||||
}
|
||||
filter := fleet.TeamFilter{User: vc.User, IncludeObserver: true}
|
||||
|
||||
hostIDs, err := svc.ds.HostIDsByName(ctx, filter, hosts)
|
||||
hostIDs, err := svc.ds.HostIDsByIdentifier(ctx, filter, hostIdentifiers)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "finding host IDs")
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func TestLiveQueryAuth(t *testing.T) {
|
||||
ds.HostIDsInTargetsFunc = func(ctx context.Context, filters fleet.TeamFilter, targets fleet.HostTargets) ([]uint, error) {
|
||||
return []uint{1}, nil
|
||||
}
|
||||
ds.HostIDsByNameFunc = func(ctx context.Context, filter fleet.TeamFilter, names []string) ([]uint, error) {
|
||||
ds.HostIDsByIdentifierFunc = func(ctx context.Context, filter fleet.TeamFilter, identifiers []string) ([]uint, error) {
|
||||
return nil, nil
|
||||
}
|
||||
ds.LabelIDsByNameFunc = func(ctx context.Context, names []string) (map[string]uint, error) {
|
||||
@@ -225,13 +225,13 @@ func TestLiveQueryAuth(t *testing.T) {
|
||||
// tests with a team target cannot run the "ByNames" calls, as there's no way
|
||||
// to pass a team target with this call.
|
||||
if tt.teamID == nil {
|
||||
_, err = svc.NewDistributedQueryCampaignByNames(ctx, query1ObsCanRun.Query, nil, nil, nil)
|
||||
_, err = svc.NewDistributedQueryCampaignByIdentifiers(ctx, query1ObsCanRun.Query, nil, nil, nil)
|
||||
checkAuthErr(t, tt.shouldFailRunNew, err)
|
||||
|
||||
_, err = svc.NewDistributedQueryCampaignByNames(ctx, query1ObsCanRun.Query, ptr.Uint(query1ObsCanRun.ID), nil, nil)
|
||||
_, err = svc.NewDistributedQueryCampaignByIdentifiers(ctx, query1ObsCanRun.Query, ptr.Uint(query1ObsCanRun.ID), nil, nil)
|
||||
checkAuthErr(t, tt.shouldFailRunObsCan, err)
|
||||
|
||||
_, err = svc.NewDistributedQueryCampaignByNames(ctx, query2ObsCannotRun.Query, ptr.Uint(query2ObsCannotRun.ID), nil, nil)
|
||||
_, err = svc.NewDistributedQueryCampaignByIdentifiers(ctx, query2ObsCannotRun.Query, ptr.Uint(query2ObsCannotRun.ID), nil, nil)
|
||||
checkAuthErr(t, tt.shouldFailRunObsCannot, err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -62,19 +62,19 @@ func (h *LiveQueryResultsHandler) Status() *campaignStatus {
|
||||
}
|
||||
|
||||
// LiveQuery creates a new live query and begins streaming results.
|
||||
func (c *Client) LiveQuery(query string, queryID *uint, labels []string, hosts []string) (*LiveQueryResultsHandler, error) {
|
||||
return c.LiveQueryWithContext(context.Background(), query, queryID, labels, hosts)
|
||||
func (c *Client) LiveQuery(query string, queryID *uint, labels []string, hostIdentifiers []string) (*LiveQueryResultsHandler, error) {
|
||||
return c.LiveQueryWithContext(context.Background(), query, queryID, labels, hostIdentifiers)
|
||||
}
|
||||
|
||||
func (c *Client) LiveQueryWithContext(
|
||||
ctx context.Context, query string, queryID *uint, labels []string, hosts []string,
|
||||
ctx context.Context, query string, queryID *uint, labels []string, hostIdentifiers []string,
|
||||
) (*LiveQueryResultsHandler, error) {
|
||||
req := createDistributedQueryCampaignByNamesRequest{
|
||||
req := createDistributedQueryCampaignByIdentifierRequest{
|
||||
QueryID: queryID,
|
||||
QuerySQL: query,
|
||||
Selected: distributedQueryCampaignTargetsByNames{Labels: labels, Hosts: hosts},
|
||||
Selected: distributedQueryCampaignTargetsByIdentifiers{Labels: labels, Hosts: hostIdentifiers},
|
||||
}
|
||||
verb, path := "POST", "/api/latest/fleet/queries/run_by_names"
|
||||
verb, path := "POST", "/api/latest/fleet/queries/run_by_identifiers"
|
||||
var responseBody createDistributedQueryCampaignResponse
|
||||
err := c.authenticatedRequest(req, verb, path, &responseBody)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestLiveQueryWithContext(t *testing.T) {
|
||||
upgrader := websocket.Upgrader{}
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/latest/fleet/queries/run_by_names":
|
||||
case "/api/latest/fleet/queries/run_by_identifiers":
|
||||
resp := createDistributedQueryCampaignResponse{
|
||||
Campaign: &fleet.DistributedQueryCampaign{
|
||||
UpdateCreateTimestamps: fleet.UpdateCreateTimestamps{
|
||||
|
||||
@@ -428,7 +428,9 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
|
||||
// The live queries are created with these two endpoints and their results can be queried via
|
||||
// websockets via the `GET /api/_version_/fleet/results/` endpoint.
|
||||
ue.POST("/api/_version_/fleet/queries/run", createDistributedQueryCampaignEndpoint, createDistributedQueryCampaignRequest{})
|
||||
ue.POST("/api/_version_/fleet/queries/run_by_names", createDistributedQueryCampaignByNamesEndpoint, createDistributedQueryCampaignByNamesRequest{})
|
||||
ue.POST("/api/_version_/fleet/queries/run_by_identifiers", createDistributedQueryCampaignByIdentifierEndpoint, createDistributedQueryCampaignByIdentifierRequest{})
|
||||
// This endpoint is deprecated and maintained for backwards compatibility. This and above endpoint are functionally equivalent
|
||||
ue.POST("/api/_version_/fleet/queries/run_by_names", createDistributedQueryCampaignByIdentifierEndpoint, createDistributedQueryCampaignByIdentifierRequest{})
|
||||
|
||||
ue.GET("/api/_version_/fleet/activities", listActivitiesEndpoint, listActivitiesRequest{})
|
||||
|
||||
|
||||
@@ -999,14 +999,14 @@ func (s *liveQueriesTestSuite) TestCreateDistributedQueryCampaign() {
|
||||
// wait to prevent duplicate name for new query
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// create by host name
|
||||
req2 := createDistributedQueryCampaignByNamesRequest{
|
||||
req2 := createDistributedQueryCampaignByIdentifierRequest{
|
||||
QuerySQL: "SELECT 3",
|
||||
Selected: distributedQueryCampaignTargetsByNames{
|
||||
Selected: distributedQueryCampaignTargetsByIdentifiers{
|
||||
Hosts: []string{h1.Hostname},
|
||||
},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries/run_by_names", req2, http.StatusOK, &createResp)
|
||||
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries/run_by_identifiers", req2, http.StatusOK, &createResp)
|
||||
assert.NotEqual(t, camp1.ID, createResp.Campaign.ID)
|
||||
assert.Equal(t, uint(1), createResp.Campaign.Metrics.TotalHosts)
|
||||
|
||||
@@ -1014,13 +1014,13 @@ func (s *liveQueriesTestSuite) TestCreateDistributedQueryCampaign() {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// create by unknown host name - it ignores the unknown names. Must have at least 1 valid host
|
||||
req2 = createDistributedQueryCampaignByNamesRequest{
|
||||
req2 = createDistributedQueryCampaignByIdentifierRequest{
|
||||
QuerySQL: "SELECT 3",
|
||||
Selected: distributedQueryCampaignTargetsByNames{
|
||||
Selected: distributedQueryCampaignTargetsByIdentifiers{
|
||||
Hosts: []string{h1.Hostname, h2.Hostname + "ZZZZZ"},
|
||||
},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries/run_by_names", req2, http.StatusOK, &createResp)
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries/run_by_identifiers", req2, http.StatusOK, &createResp)
|
||||
}
|
||||
|
||||
func (s *liveQueriesTestSuite) TestOsqueryDistributedRead() {
|
||||
|
||||
Reference in New Issue
Block a user