diff --git a/cmd/fleetctl/fleetctl/flags.go b/cmd/fleetctl/fleetctl/flags.go
index 0050d95ced..847cce2952 100644
--- a/cmd/fleetctl/fleetctl/flags.go
+++ b/cmd/fleetctl/fleetctl/flags.go
@@ -71,3 +71,10 @@ func byMDMCommandRequestType() cli.Flag {
Usage: "Filter MDM commands by type.",
}
}
+
+func withMDMCommandStatusFilter() cli.Flag {
+ return &cli.StringFlag{
+ Name: "command_status",
+ Usage: "Filter MDM commands by command status in a comma-separated list. Valid values are 'pending', 'ran', and 'failed'. ",
+ }
+}
diff --git a/cmd/fleetctl/fleetctl/get.go b/cmd/fleetctl/fleetctl/get.go
index 0a7dca4637..771a21cfe6 100644
--- a/cmd/fleetctl/fleetctl/get.go
+++ b/cmd/fleetctl/fleetctl/get.go
@@ -1473,6 +1473,7 @@ func getMDMCommandResultsCommand() *cli.Command {
Usage: "Filter MDM commands by ID.",
Required: true,
},
+ byHostIdentifier(),
},
Action: func(c *cli.Context) error {
client, err := clientFromCLI(c)
@@ -1485,10 +1486,10 @@ func getMDMCommandResultsCommand() *cli.Command {
return err
}
- res, err := client.MDMGetCommandResults(c.String("id"))
+ res, err := client.MDMGetCommandResults(c.String("id"), c.String("host"))
if err != nil {
var nfe service.NotFoundErr
- if errors.As(err, &nfe) {
+ if errors.As(err, &nfe) && c.String("host") == "" {
return errors.New("The command doesn't exist. Please provide a valid command ID. To see a list of commands that were run, run `fleetctl get mdm-commands`.")
}
@@ -1569,6 +1570,7 @@ func getMDMCommandsCommand() *cli.Command {
debugFlag(),
byHostIdentifier(),
byMDMCommandRequestType(),
+ withMDMCommandStatusFilter(),
},
Action: func(c *cli.Context) error {
client, err := clientFromCLI(c)
@@ -1581,10 +1583,18 @@ func getMDMCommandsCommand() *cli.Command {
return err
}
+ commandStatuses := []fleet.MDMCommandStatusFilter{}
+ if c.IsSet("command_status") {
+ for val := range strings.SplitSeq(c.String("command_status"), ",") {
+ commandStatuses = append(commandStatuses, fleet.MDMCommandStatusFilter(val))
+ }
+ }
+
opts := fleet.MDMCommandListOptions{
Filters: fleet.MDMCommandFilters{
- HostIdentifier: c.String("host"),
- RequestType: c.String("type"),
+ HostIdentifier: c.String("host"),
+ RequestType: c.String("type"),
+ CommandStatuses: commandStatuses,
},
}
@@ -1595,7 +1605,13 @@ func getMDMCommandsCommand() *cli.Command {
}
return err
}
- if len(results) == 0 && opts.Filters.HostIdentifier == "" && opts.Filters.RequestType == "" {
+
+ if len(results) == 0 {
+ if opts.Filters.HostIdentifier != "" {
+ log(c, "No MDM commands have been run on this host.\n")
+ return nil
+ }
+
log(c, "You haven't run any MDM commands. Run MDM commands with the `fleetctl mdm run-command` command.\n")
return nil
}
diff --git a/cmd/fleetctl/fleetctl/get_test.go b/cmd/fleetctl/fleetctl/get_test.go
index 115ad0465a..4bc67d1cad 100644
--- a/cmd/fleetctl/fleetctl/get_test.go
+++ b/cmd/fleetctl/fleetctl/get_test.go
@@ -2593,6 +2593,18 @@ func TestGetMDMCommandResults(t *testing.T) {
{ID: 2, UUID: uuids[1], Hostname: "host2"},
}, nil
}
+ ds.GetHostMDMIdentifiersFunc = func(ctx context.Context, identifer string, teamFilter fleet.TeamFilter) ([]*fleet.HostMDMIdentifiers, error) {
+ return []*fleet.HostMDMIdentifiers{
+ {
+ UUID: "device1",
+ HardwareSerial: "C02XXXXXXX1",
+ Hostname: "host1",
+ ID: 1,
+ TeamID: ptr.Uint(1),
+ Platform: "darwin",
+ },
+ }, nil
+ }
ds.GetMDMAppleCommandResultsFunc = func(ctx context.Context, commandUUID string, hostUUID string) ([]*fleet.MDMCommandResult, error) {
switch commandUUID {
case "empty-cmd":
@@ -3000,6 +3012,66 @@ RESULTS:
ds.GetMDMWindowsCommandResultsFuncInvoked = false
require.False(t, ds.GetMDMAppleCommandResultsFuncInvoked)
})
+
+ t.Run("host specific results", func(t *testing.T) {
+ expectedOutput := strings.TrimSpace(`
+ID:
+valid-cmd
+
+TIME:
+2023-04-04T15:29:00Z
+
+TYPE:
+test
+
+STATUS:
+Acknowledged
+
+HOSTNAME:
+host1
+
+PAYLOAD:
+
+
+
+
+ Command
+
+ ManagedOnly
+
+ RequestType
+ ProfileList
+
+ CommandUUID
+ 0001_ProfileList
+
+
+
+
+RESULTS:
+
+
+
+
+ CommandUUID
+ 6d7cb698-8d93-45a3-b544-71aef37d42e8
+ Status
+ Acknowledged
+ UDID
+ 419D46EC-06E6-557C-AD52-601BA0667730
+
+`)
+
+ platform = "darwin"
+ buf, err := RunAppNoChecks([]string{"get", "mdm-command-results", "--id", "valid-cmd", "--host", "device1"})
+ require.NoError(t, err)
+ require.Contains(t, buf.String(), expectedOutput)
+ require.True(t, ds.GetMDMCommandPlatformFuncInvoked)
+ ds.GetMDMCommandPlatformFuncInvoked = false
+ require.False(t, ds.GetMDMWindowsCommandResultsFuncInvoked)
+ require.True(t, ds.GetMDMAppleCommandResultsFuncInvoked)
+ ds.GetMDMAppleCommandResultsFuncInvoked = false
+ })
}
func TestGetMDMCommands(t *testing.T) {
@@ -3008,6 +3080,14 @@ func TestGetMDMCommands(t *testing.T) {
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{MDM: fleet.MDM{EnabledAndConfigured: true}}, nil
}
+ ds.HostLiteByIdentifierFunc = func(ctx context.Context, identifier string) (*fleet.HostLite, error) {
+ fmt.Println("Called", identifier)
+ if identifier == "foo" || identifier == "h1" {
+ return &fleet.HostLite{ID: 1, UUID: "h1", Hostname: "host1"}, nil
+ }
+ return nil, errors.New(fleet.HostIdentiferNotFound)
+ }
+
var empty bool
var listErr error
var noHostErr error
@@ -3029,7 +3109,7 @@ func TestGetMDMCommands(t *testing.T) {
if expectRequestType {
require.NotEmpty(t, listOpts.Filters.RequestType)
}
-
+ fmt.Println("Returning commands")
return []*fleet.MDMCommand{
{
HostUUID: "h1",
@@ -3123,6 +3203,20 @@ The list of 3 most recent commands:
_, err = RunAppNoChecks([]string{"get", "mdm-commands", "--host", "foo"})
require.Error(t, err)
require.ErrorContains(t, err, fleet.HostIdentiferNotFound)
+
+ // Empty results when using host identifier
+ listErr = nil
+ empty = true
+ expectIdentifier = true
+ noHostErr = nil
+ buf, err = RunAppNoChecks([]string{"get", "mdm-commands", "--host", "foo"})
+ require.NoError(t, err)
+ require.Contains(t, buf.String(), "No MDM commands have been run on this host.")
+
+ // Command status no host
+ _, err = RunAppNoChecks([]string{"get", "mdm-commands", "--command_status", "ran"})
+ require.Error(t, err)
+ require.ErrorContains(t, err, `"host_identifier" must be specified when filtering by "command_status"`)
}
func TestUserIsObserver(t *testing.T) {
diff --git a/server/service/client_mdm.go b/server/service/client_mdm.go
index 4f41f1b57d..3429d93c1b 100644
--- a/server/service/client_mdm.go
+++ b/server/service/client_mdm.go
@@ -283,7 +283,7 @@ func (c *Client) deleteMacOSSetupAssistant(teamID *uint) error {
func (c *Client) MDMListCommands(opts fleet.MDMCommandListOptions) ([]*fleet.MDMCommand, error) {
const defaultCommandsPerPage = 20
- verb, path := http.MethodGet, "/api/latest/fleet/mdm/commands"
+ verb, path := http.MethodGet, "/api/latest/fleet/commands"
query := url.Values{}
query.Set("per_page", fmt.Sprint(defaultCommandsPerPage))
@@ -292,6 +292,15 @@ func (c *Client) MDMListCommands(opts fleet.MDMCommandListOptions) ([]*fleet.MDM
query.Set("host_identifier", opts.Filters.HostIdentifier)
query.Set("request_type", opts.Filters.RequestType)
+ var statuses []string
+ if len(opts.Filters.CommandStatuses) > 0 {
+ statuses = make([]string, 0, len(opts.Filters.CommandStatuses))
+ for _, s := range opts.Filters.CommandStatuses {
+ statuses = append(statuses, string(s))
+ }
+ }
+ query.Set("command_status", strings.Join(statuses, ","))
+
var responseBody listMDMCommandsResponse
err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query.Encode())
if err != nil {
@@ -301,11 +310,12 @@ func (c *Client) MDMListCommands(opts fleet.MDMCommandListOptions) ([]*fleet.MDM
return responseBody.Results, nil
}
-func (c *Client) MDMGetCommandResults(commandUUID string) ([]*fleet.MDMCommandResult, error) {
- verb, path := http.MethodGet, "/api/latest/fleet/mdm/commandresults"
+func (c *Client) MDMGetCommandResults(commandUUID, hostIdentifier string) ([]*fleet.MDMCommandResult, error) {
+ verb, path := http.MethodGet, "/api/latest/fleet/commands/results"
query := url.Values{}
query.Set("command_uuid", commandUUID)
+ query.Set("host_identifier", hostIdentifier)
var responseBody getMDMCommandResultsResponse
err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query.Encode())
diff --git a/server/service/mdm.go b/server/service/mdm.go
index a7c7c3aa36..7452b92c8b 100644
--- a/server/service/mdm.go
+++ b/server/service/mdm.go
@@ -1060,7 +1060,6 @@ func (svc *Service) ListMDMCommands(ctx context.Context, opts *fleet.MDMCommandL
return nil, nil, fleet.NewInvalidArgumentError("Invalid Host", fleet.HostIdentiferNotFound).WithStatus(http.StatusNotFound)
}
}
-
return results, total, nil
}