mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
This reverts commit 4b2ebdc8dc.
This commit is contained in:
parent
447baf32d3
commit
2e67ef61d4
8 changed files with 70 additions and 403 deletions
|
|
@ -970,7 +970,7 @@ type ListOptions struct {
|
||||||
// MatchQuery is the query string to match against columns of the entity
|
// MatchQuery is the query string to match against columns of the entity
|
||||||
// (varies depending on entity, eg. hostname, IP address for hosts).
|
// (varies depending on entity, eg. hostname, IP address for hosts).
|
||||||
// Handling for this parameter must be implemented separately for each type.
|
// Handling for this parameter must be implemented separately for each type.
|
||||||
MatchQuery string `query:"query,optional" json:"query,omitempty"`
|
MatchQuery string `query:"query,optional"`
|
||||||
// After denotes the row to start from. This is meant to be used in conjunction with OrderKey
|
// After denotes the row to start from. This is meant to be used in conjunction with OrderKey
|
||||||
// If OrderKey is "id", it'll assume After is a number and will try to convert it.
|
// If OrderKey is "id", it'll assume After is a number and will try to convert it.
|
||||||
After string `query:"after,optional"`
|
After string `query:"after,optional"`
|
||||||
|
|
|
||||||
|
|
@ -39,15 +39,6 @@ const (
|
||||||
OnlineIntervalBuffer = 60
|
OnlineIntervalBuffer = 60
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s HostStatus) IsValid() bool {
|
|
||||||
switch s {
|
|
||||||
case StatusOnline, StatusOffline, StatusNew, StatusMissing, StatusMIA:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MDMEnrollStatus defines the possible MDM enrollment statuses.
|
// MDMEnrollStatus defines the possible MDM enrollment statuses.
|
||||||
type MDMEnrollStatus string
|
type MDMEnrollStatus string
|
||||||
|
|
||||||
|
|
@ -59,15 +50,6 @@ const (
|
||||||
MDMEnrollStatusEnrolled = MDMEnrollStatus("enrolled") // combination of "manual" and "automatic"
|
MDMEnrollStatusEnrolled = MDMEnrollStatus("enrolled") // combination of "manual" and "automatic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s MDMEnrollStatus) IsValid() bool {
|
|
||||||
switch s {
|
|
||||||
case MDMEnrollStatusManual, MDMEnrollStatusAutomatic, MDMEnrollStatusPending, MDMEnrollStatusUnenrolled, MDMEnrollStatusEnrolled:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OSSettingsStatus defines the possible statuses of the host's OS settings, which is derived from the
|
// OSSettingsStatus defines the possible statuses of the host's OS settings, which is derived from the
|
||||||
// status of MDM configuration profiles and non-profile settings applied the host.
|
// status of MDM configuration profiles and non-profile settings applied the host.
|
||||||
type OSSettingsStatus string
|
type OSSettingsStatus string
|
||||||
|
|
@ -137,15 +119,12 @@ type HostListOptions struct {
|
||||||
// populated.
|
// populated.
|
||||||
AdditionalFilters []string
|
AdditionalFilters []string
|
||||||
// StatusFilter selects the online status of the hosts.
|
// StatusFilter selects the online status of the hosts.
|
||||||
StatusFilter HostStatus `json:"status"`
|
StatusFilter HostStatus
|
||||||
// TeamFilter selects the hosts for specified team
|
// TeamFilter selects the hosts for specified team
|
||||||
TeamFilter *uint `json:"team_id"`
|
TeamFilter *uint
|
||||||
|
|
||||||
PolicyIDFilter *uint `json:"policy_id"`
|
PolicyIDFilter *uint
|
||||||
PolicyResponseFilterRequest *string `json:"policy_response"`
|
PolicyResponseFilter *bool
|
||||||
PolicyResponseFilter *bool
|
|
||||||
|
|
||||||
LabelID *uint `json:"label_id"`
|
|
||||||
|
|
||||||
// Deprecated: SoftwareIDFilter is deprecated as of Fleet 4.42. It is
|
// Deprecated: SoftwareIDFilter is deprecated as of Fleet 4.42. It is
|
||||||
// maintained for backwards compatibility. Use SoftwareVersionIDFilter
|
// maintained for backwards compatibility. Use SoftwareVersionIDFilter
|
||||||
|
|
@ -153,16 +132,16 @@ type HostListOptions struct {
|
||||||
SoftwareIDFilter *uint
|
SoftwareIDFilter *uint
|
||||||
// SoftwareVersionIDFilter filters the hosts by the software version ID that
|
// SoftwareVersionIDFilter filters the hosts by the software version ID that
|
||||||
// they use. This identifies a specific version of a "software title".
|
// they use. This identifies a specific version of a "software title".
|
||||||
SoftwareVersionIDFilter *uint `json:"software_version_id"`
|
SoftwareVersionIDFilter *uint
|
||||||
// SoftwareTitleIDFilter filers the hosts by the software title ID that they
|
// SoftwareTitleIDFilter filers the hosts by the software title ID that they
|
||||||
// use. This identifies a "software title" independent of the specific
|
// use. This identifies a "software title" independent of the specific
|
||||||
// version.
|
// version.
|
||||||
SoftwareTitleIDFilter *uint `json:"software_title_id"`
|
SoftwareTitleIDFilter *uint
|
||||||
|
|
||||||
OSIDFilter *uint
|
OSIDFilter *uint
|
||||||
OSNameFilter *string `json:"os_name"`
|
OSNameFilter *string
|
||||||
OSVersionFilter *string `json:"os_version"`
|
OSVersionFilter *string
|
||||||
OSVersionIDFilter *uint `json:"os_version_id"`
|
OSVersionIDFilter *uint
|
||||||
|
|
||||||
DisableFailingPolicies bool
|
DisableFailingPolicies bool
|
||||||
|
|
||||||
|
|
@ -176,29 +155,29 @@ type HostListOptions struct {
|
||||||
|
|
||||||
// OSSettingsFilter filters the hosts by the status of MDM configuration profiles and
|
// OSSettingsFilter filters the hosts by the status of MDM configuration profiles and
|
||||||
// non-profile settings applied to the hosts.
|
// non-profile settings applied to the hosts.
|
||||||
OSSettingsFilter OSSettingsStatus `json:"os_settings"`
|
OSSettingsFilter OSSettingsStatus
|
||||||
// OSSettingsDiskEncryptionFilter filters the hosts by the status of the disk encryption
|
// OSSettingsDiskEncryptionFilter filters the hosts by the status of the disk encryption
|
||||||
// OS setting.
|
// OS setting.
|
||||||
OSSettingsDiskEncryptionFilter DiskEncryptionStatus `json:"os_settings_disk_encryption"`
|
OSSettingsDiskEncryptionFilter DiskEncryptionStatus
|
||||||
|
|
||||||
// MDMBootstrapPackageFilter filters the hosts by the status of the MDM bootstrap package.
|
// MDMBootstrapPackageFilter filters the hosts by the status of the MDM bootstrap package.
|
||||||
MDMBootstrapPackageFilter *MDMBootstrapPackageStatus `json:"bootstrap_package"`
|
MDMBootstrapPackageFilter *MDMBootstrapPackageStatus
|
||||||
|
|
||||||
// MDMIDFilter filters the hosts by MDM ID.
|
// MDMIDFilter filters the hosts by MDM ID.
|
||||||
MDMIDFilter *uint `json:"mdm_id"`
|
MDMIDFilter *uint
|
||||||
// MDMNameFilter filters the hosts by MDM solution name (e.g. one of the
|
// MDMNameFilter filters the hosts by MDM solution name (e.g. one of the
|
||||||
// fleet.WellKnownMDM... constants).
|
// fleet.WellKnownMDM... constants).
|
||||||
MDMNameFilter *string `json:"mdm_name"`
|
MDMNameFilter *string
|
||||||
// MDMEnrollmentStatusFilter filters the host by their MDM enrollment status.
|
// MDMEnrollmentStatusFilter filters the host by their MDM enrollment status.
|
||||||
MDMEnrollmentStatusFilter MDMEnrollStatus `json:"mdm_enrollment_status"`
|
MDMEnrollmentStatusFilter MDMEnrollStatus
|
||||||
// MunkiIssueIDFilter filters the hosts by munki issue ID.
|
// MunkiIssueIDFilter filters the hosts by munki issue ID.
|
||||||
MunkiIssueIDFilter *uint `json:"munki_issue_id"`
|
MunkiIssueIDFilter *uint
|
||||||
|
|
||||||
// LowDiskSpaceFilter filters the hosts by low disk space (defined as a host
|
// LowDiskSpaceFilter filters the hosts by low disk space (defined as a host
|
||||||
// with less than N gigs of disk space available). Note that this is a Fleet
|
// with less than N gigs of disk space available). Note that this is a Fleet
|
||||||
// Premium feature, Fleet Free ignores the setting (it forces it to nil to
|
// Premium feature, Fleet Free ignores the setting (it forces it to nil to
|
||||||
// disable it).
|
// disable it).
|
||||||
LowDiskSpaceFilter *int `json:"low_disk_space"`
|
LowDiskSpaceFilter *int
|
||||||
|
|
||||||
// PopulateSoftware adds the `Software` field to all Hosts returned.
|
// PopulateSoftware adds the `Software` field to all Hosts returned.
|
||||||
PopulateSoftware bool
|
PopulateSoftware bool
|
||||||
|
|
@ -207,7 +186,7 @@ type HostListOptions struct {
|
||||||
PopulatePolicies bool
|
PopulatePolicies bool
|
||||||
|
|
||||||
// VulnerabilityFilter filters the hosts by the presence of a vulnerability (CVE)
|
// VulnerabilityFilter filters the hosts by the presence of a vulnerability (CVE)
|
||||||
VulnerabilityFilter *string `json:"vulnerability"`
|
VulnerabilityFilter *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Sarah): Are we missing any filters here? Should all MDM filters be included?
|
// TODO(Sarah): Are we missing any filters here? Should all MDM filters be included?
|
||||||
|
|
|
||||||
|
|
@ -52,46 +52,6 @@ func TestHostStatus(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHostStatusIsValid(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
name string
|
|
||||||
status HostStatus
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{"online", StatusOnline, true},
|
|
||||||
{"offline", StatusOffline, true},
|
|
||||||
{"new", StatusNew, true},
|
|
||||||
{"missing", StatusMissing, true},
|
|
||||||
{"mia", StatusMIA, true}, // As of Fleet 4.15, StatusMIA is deprecated in favor of StatusOffline
|
|
||||||
{"empty", HostStatus(""), false},
|
|
||||||
{"invalid", HostStatus("invalid"), false},
|
|
||||||
} {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, tt.expected, tt.status.IsValid())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMDMEnrollStatusIsValid(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
name string
|
|
||||||
status MDMEnrollStatus
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{"manual", MDMEnrollStatusManual, true},
|
|
||||||
{"automatic", MDMEnrollStatusAutomatic, true},
|
|
||||||
{"pending", MDMEnrollStatusPending, true},
|
|
||||||
{"unenrolled", MDMEnrollStatusUnenrolled, true},
|
|
||||||
{"enrolled", MDMEnrollStatusEnrolled, true},
|
|
||||||
{"empty", MDMEnrollStatus(""), false},
|
|
||||||
{"invalid", MDMEnrollStatus("invalid"), false},
|
|
||||||
} {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, tt.expected, tt.status.IsValid())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostIsNew(t *testing.T) {
|
func TestHostIsNew(t *testing.T) {
|
||||||
mockClock := clock.NewMockClock()
|
mockClock := clock.NewMockClock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -350,7 +350,7 @@ type Service interface {
|
||||||
AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint, skipBulkPending bool) error
|
AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint, skipBulkPending bool) error
|
||||||
// AddHostsToTeamByFilter adds hosts to an existing team, clearing their team settings if teamID is nil. Hosts are
|
// AddHostsToTeamByFilter adds hosts to an existing team, clearing their team settings if teamID is nil. Hosts are
|
||||||
// selected by the label and HostListOptions provided.
|
// selected by the label and HostListOptions provided.
|
||||||
AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt *HostListOptions, lid *uint) error
|
AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt HostListOptions, lid *uint) error
|
||||||
DeleteHosts(ctx context.Context, ids []uint, opt *HostListOptions, lid *uint) error
|
DeleteHosts(ctx context.Context, ids []uint, opt *HostListOptions, lid *uint) error
|
||||||
CountHosts(ctx context.Context, labelID *uint, opts HostListOptions) (int, error)
|
CountHosts(ctx context.Context, labelID *uint, opts HostListOptions) (int, error)
|
||||||
// SearchHosts performs a search on the hosts table using the following criteria:
|
// SearchHosts performs a search on the hosts table using the following criteria:
|
||||||
|
|
|
||||||
|
|
@ -122,16 +122,13 @@ func (c *Client) TransferHosts(hosts []string, label string, status, searchQuery
|
||||||
verb, path := "POST", "/api/latest/fleet/hosts/transfer/filter"
|
verb, path := "POST", "/api/latest/fleet/hosts/transfer/filter"
|
||||||
var responseBody addHostsToTeamByFilterResponse
|
var responseBody addHostsToTeamByFilterResponse
|
||||||
params := addHostsToTeamByFilterRequest{
|
params := addHostsToTeamByFilterRequest{
|
||||||
TeamID: teamIDPtr,
|
TeamID: teamIDPtr, Filters: struct {
|
||||||
Filters: &fleet.HostListOptions{
|
MatchQuery string `json:"query"`
|
||||||
ListOptions: fleet.ListOptions{
|
Status fleet.HostStatus `json:"status"`
|
||||||
MatchQuery: searchQuery,
|
LabelID *uint `json:"label_id"`
|
||||||
},
|
TeamID *uint `json:"team_id"`
|
||||||
LabelID: labelIDPtr,
|
}{MatchQuery: searchQuery, Status: fleet.HostStatus(status), LabelID: labelIDPtr},
|
||||||
StatusFilter: fleet.HostStatus(status),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.authenticatedRequest(params, verb, path, &responseBody)
|
return c.authenticatedRequest(params, verb, path, &responseBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -217,10 +217,17 @@ var (
|
||||||
deleteHostsSkipAuthorization = false
|
deleteHostsSkipAuthorization = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type deleteHostsFilters struct {
|
||||||
|
MatchQuery string `json:"query"`
|
||||||
|
Status fleet.HostStatus `json:"status"`
|
||||||
|
LabelID *uint `json:"label_id"`
|
||||||
|
TeamID *uint `json:"team_id"`
|
||||||
|
}
|
||||||
|
|
||||||
type deleteHostsRequest struct {
|
type deleteHostsRequest struct {
|
||||||
IDs []uint `json:"ids"`
|
IDs []uint `json:"ids"`
|
||||||
// Using a pointer to help determine whether an empty filter was passed, like: "filters":{}
|
// Using a pointer to help determine whether an empty filter was passed, like: "filters":{}
|
||||||
Filters *fleet.HostListOptions `json:"filters"`
|
Filters *deleteHostsFilters `json:"filters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type deleteHostsResponse struct {
|
type deleteHostsResponse struct {
|
||||||
|
|
@ -235,9 +242,16 @@ func (r deleteHostsResponse) Status() int { return r.StatusCode }
|
||||||
|
|
||||||
func deleteHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
func deleteHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||||
req := request.(*deleteHostsRequest)
|
req := request.(*deleteHostsRequest)
|
||||||
|
var listOpts *fleet.HostListOptions
|
||||||
var labelID *uint
|
var labelID *uint
|
||||||
if req.Filters != nil {
|
if req.Filters != nil {
|
||||||
|
listOpts = &fleet.HostListOptions{
|
||||||
|
ListOptions: fleet.ListOptions{
|
||||||
|
MatchQuery: req.Filters.MatchQuery,
|
||||||
|
},
|
||||||
|
StatusFilter: req.Filters.Status,
|
||||||
|
TeamFilter: req.Filters.TeamID,
|
||||||
|
}
|
||||||
labelID = req.Filters.LabelID
|
labelID = req.Filters.LabelID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,7 +261,7 @@ func deleteHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Ser
|
||||||
deleteDone := make(chan bool, 1)
|
deleteDone := make(chan bool, 1)
|
||||||
ctx = context.WithoutCancel(ctx) // to make sure DB operations don't get killed after we return a 202
|
ctx = context.WithoutCancel(ctx) // to make sure DB operations don't get killed after we return a 202
|
||||||
go func() {
|
go func() {
|
||||||
err = svc.DeleteHosts(ctx, req.IDs, req.Filters, labelID)
|
err = svc.DeleteHosts(ctx, req.IDs, listOpts, labelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// logging the error for future debug in case we already sent http.StatusAccepted
|
// logging the error for future debug in case we already sent http.StatusAccepted
|
||||||
logging.WithErr(ctx, err)
|
logging.WithErr(ctx, err)
|
||||||
|
|
@ -270,13 +284,7 @@ func deleteHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Ser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) DeleteHosts(ctx context.Context, ids []uint, opts *fleet.HostListOptions, lid *uint) error {
|
func (svc *Service) DeleteHosts(ctx context.Context, ids []uint, opts *fleet.HostListOptions, lid *uint) error {
|
||||||
var err error
|
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
|
||||||
if err = svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err = validateAndPopulateHostListOptionsFilters(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -854,8 +862,13 @@ func (svc *Service) createTransferredHostsActivity(ctx context.Context, teamID *
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
type addHostsToTeamByFilterRequest struct {
|
type addHostsToTeamByFilterRequest struct {
|
||||||
TeamID *uint `json:"team_id"`
|
TeamID *uint `json:"team_id"`
|
||||||
Filters *fleet.HostListOptions `json:"filters"`
|
Filters struct {
|
||||||
|
MatchQuery string `json:"query"`
|
||||||
|
Status fleet.HostStatus `json:"status"`
|
||||||
|
LabelID *uint `json:"label_id"`
|
||||||
|
TeamID *uint `json:"team_id"`
|
||||||
|
} `json:"filters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type addHostsToTeamByFilterResponse struct {
|
type addHostsToTeamByFilterResponse struct {
|
||||||
|
|
@ -866,8 +879,14 @@ func (r addHostsToTeamByFilterResponse) error() error { return r.Err }
|
||||||
|
|
||||||
func addHostsToTeamByFilterEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
func addHostsToTeamByFilterEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||||
req := request.(*addHostsToTeamByFilterRequest)
|
req := request.(*addHostsToTeamByFilterRequest)
|
||||||
|
listOpt := fleet.HostListOptions{
|
||||||
err := svc.AddHostsToTeamByFilter(ctx, req.TeamID, req.Filters, req.Filters.LabelID)
|
ListOptions: fleet.ListOptions{
|
||||||
|
MatchQuery: req.Filters.MatchQuery,
|
||||||
|
},
|
||||||
|
StatusFilter: req.Filters.Status,
|
||||||
|
TeamFilter: req.Filters.TeamID,
|
||||||
|
}
|
||||||
|
err := svc.AddHostsToTeamByFilter(ctx, req.TeamID, listOpt, req.Filters.LabelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return addHostsToTeamByFilterResponse{Err: err}, nil
|
return addHostsToTeamByFilterResponse{Err: err}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -875,7 +894,7 @@ func addHostsToTeamByFilterEndpoint(ctx context.Context, request interface{}, sv
|
||||||
return addHostsToTeamByFilterResponse{}, err
|
return addHostsToTeamByFilterResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt *fleet.HostListOptions, lid *uint) error {
|
func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt fleet.HostListOptions, lid *uint) error {
|
||||||
// This is currently treated as a "team write". If we ever give users
|
// This is currently treated as a "team write". If we ever give users
|
||||||
// besides global admins permissions to modify team hosts, we will need to
|
// besides global admins permissions to modify team hosts, we will need to
|
||||||
// check that the user has permissions for both the source and destination
|
// check that the user has permissions for both the source and destination
|
||||||
|
|
@ -884,16 +903,7 @@ func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, op
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt == nil {
|
hostIDs, hostNames, err := svc.hostIDsAndNamesFromFilters(ctx, opt, lid)
|
||||||
return &fleet.BadRequestError{Message: "filters must be specified"}
|
|
||||||
}
|
|
||||||
|
|
||||||
opt, err := validateAndPopulateHostListOptionsFilters(ctx, opt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostIDs, hostNames, err := svc.hostIDsAndNamesFromFilters(ctx, *opt, lid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -2150,63 +2160,3 @@ func (svc *Service) HostLiteByID(ctx context.Context, id uint) (*fleet.HostLite,
|
||||||
|
|
||||||
return host, nil
|
return host, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAndPopulateHostListOptionsFilters(ctx context.Context, opt *fleet.HostListOptions) (*fleet.HostListOptions, error) {
|
|
||||||
if opt == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.StatusFilter != "" && !opt.StatusFilter.IsValid() {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid status %s", opt.StatusFilter)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.PolicyResponseFilterRequest != nil && opt.PolicyIDFilter == nil {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest("Policy ID must be provided when filtering by policy response"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.PolicyResponseFilterRequest != nil {
|
|
||||||
if *opt.PolicyResponseFilterRequest == "passing" {
|
|
||||||
opt.PolicyResponseFilter = ptr.Bool(true)
|
|
||||||
} else if *opt.PolicyResponseFilterRequest == "failing" {
|
|
||||||
opt.PolicyResponseFilter = ptr.Bool(false)
|
|
||||||
} else {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid policy response filter %s", *opt.PolicyResponseFilterRequest)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.SoftwareTitleIDFilter != nil && opt.SoftwareVersionIDFilter != nil {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest("Software title ID and name cannot be used together"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.OSNameFilter != nil && opt.OSVersionFilter == nil {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest("OS version must be provided when filtering by OS name"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.OSNameFilter == nil && opt.OSVersionFilter != nil {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest("OS name must be provided when filtering by OS version"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.MDMEnrollmentStatusFilter != "" && !opt.MDMEnrollmentStatusFilter.IsValid() {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid MDM enrollment status %s", opt.MDMEnrollmentStatusFilter)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.OSSettingsFilter != "" && !opt.OSSettingsFilter.IsValid() {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid OS settings status %s", opt.OSSettingsFilter)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.OSSettingsDiskEncryptionFilter != "" && !opt.OSSettingsDiskEncryptionFilter.IsValid() {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid disk encryption status %s", opt.OSSettingsDiskEncryptionFilter)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.MDMBootstrapPackageFilter != nil && !opt.MDMBootstrapPackageFilter.IsValid() {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid MDM bootstrap status %s", *opt.MDMBootstrapPackageFilter)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.LowDiskSpaceFilter != nil {
|
|
||||||
if *opt.LowDiskSpaceFilter > 100 || *opt.LowDiskSpaceFilter < 1 {
|
|
||||||
return opt, ctxerr.Wrap(ctx, badRequest("Low disk space filter must be between 1 and 100"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return opt, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -734,7 +732,7 @@ func TestHostAuth(t *testing.T) {
|
||||||
err = svc.AddHostsToTeam(ctx, ptr.Uint(1), []uint{1}, false)
|
err = svc.AddHostsToTeam(ctx, ptr.Uint(1), []uint{1}, false)
|
||||||
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
||||||
|
|
||||||
err = svc.AddHostsToTeamByFilter(ctx, ptr.Uint(1), &fleet.HostListOptions{}, nil)
|
err = svc.AddHostsToTeamByFilter(ctx, ptr.Uint(1), fleet.HostListOptions{}, nil)
|
||||||
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
||||||
|
|
||||||
err = svc.RefetchHost(ctx, 1)
|
err = svc.RefetchHost(ctx, 1)
|
||||||
|
|
@ -857,7 +855,7 @@ func TestAddHostsToTeamByFilter(t *testing.T) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, &fleet.HostListOptions{}, nil))
|
require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, fleet.HostListOptions{}, nil))
|
||||||
assert.True(t, ds.ListHostsFuncInvoked)
|
assert.True(t, ds.ListHostsFuncInvoked)
|
||||||
assert.True(t, ds.AddHostsToTeamFuncInvoked)
|
assert.True(t, ds.AddHostsToTeamFuncInvoked)
|
||||||
}
|
}
|
||||||
|
|
@ -895,7 +893,7 @@ func TestAddHostsToTeamByFilterLabel(t *testing.T) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, &fleet.HostListOptions{}, expectedLabel))
|
require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, fleet.HostListOptions{}, expectedLabel))
|
||||||
assert.True(t, ds.ListHostsInLabelFuncInvoked)
|
assert.True(t, ds.ListHostsInLabelFuncInvoked)
|
||||||
assert.True(t, ds.AddHostsToTeamFuncInvoked)
|
assert.True(t, ds.AddHostsToTeamFuncInvoked)
|
||||||
}
|
}
|
||||||
|
|
@ -914,7 +912,7 @@ func TestAddHostsToTeamByFilterEmptyHosts(t *testing.T) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), nil, &fleet.HostListOptions{}, nil))
|
require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), nil, fleet.HostListOptions{}, nil))
|
||||||
assert.True(t, ds.ListHostsFuncInvoked)
|
assert.True(t, ds.ListHostsFuncInvoked)
|
||||||
assert.False(t, ds.AddHostsToTeamFuncInvoked)
|
assert.False(t, ds.AddHostsToTeamFuncInvoked)
|
||||||
}
|
}
|
||||||
|
|
@ -1630,220 +1628,3 @@ func TestLockUnlockWipeHostAuth(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateAndPopulateHostListOptionsFilters(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
jsonBody string
|
|
||||||
expected *fleet.HostListOptions
|
|
||||||
has400Error bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no filter",
|
|
||||||
jsonBody: `{
|
|
||||||
"somevalue": "somevalue"
|
|
||||||
}`,
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty filter",
|
|
||||||
jsonBody: `{
|
|
||||||
"somevalue": "somevalue",
|
|
||||||
"filter": {}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "all valid filters",
|
|
||||||
jsonBody: `{
|
|
||||||
"somevalue": "somevalue",
|
|
||||||
"filter": {
|
|
||||||
"query": "foo",
|
|
||||||
"status": "new",
|
|
||||||
"team_id": 1,
|
|
||||||
"policy_id": 2,
|
|
||||||
"policy_response": "passing",
|
|
||||||
"os_name": "macOS",
|
|
||||||
"os_version": "11.1",
|
|
||||||
"os_version_id": 3,
|
|
||||||
"os_settings": "pending",
|
|
||||||
"os_settings_disk_encryption": "failed",
|
|
||||||
"bootstrap_package": "installed",
|
|
||||||
"munki_issue_id": 4,
|
|
||||||
"vulnerability": "CVE-2021-1234",
|
|
||||||
"mdm_id": 4,
|
|
||||||
"mdm_name": "mdm_name",
|
|
||||||
"mdm_enrollment_status": "automatic",
|
|
||||||
"low_disk_space": 99
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{
|
|
||||||
ListOptions: fleet.ListOptions{
|
|
||||||
MatchQuery: "foo",
|
|
||||||
},
|
|
||||||
StatusFilter: fleet.StatusNew,
|
|
||||||
TeamFilter: ptr.Uint(1),
|
|
||||||
PolicyIDFilter: ptr.Uint(2),
|
|
||||||
PolicyResponseFilter: ptr.Bool(true),
|
|
||||||
PolicyResponseFilterRequest: ptr.String("passing"),
|
|
||||||
OSNameFilter: ptr.String("macOS"),
|
|
||||||
OSVersionFilter: ptr.String("11.1"),
|
|
||||||
OSVersionIDFilter: ptr.Uint(3),
|
|
||||||
OSSettingsFilter: fleet.OSSettingsPending,
|
|
||||||
OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionFailed,
|
|
||||||
MDMBootstrapPackageFilter: (*fleet.MDMBootstrapPackageStatus)(ptr.String(string(fleet.MDMBootstrapPackageInstalled))),
|
|
||||||
MunkiIssueIDFilter: ptr.Uint(4),
|
|
||||||
VulnerabilityFilter: ptr.String("CVE-2021-1234"),
|
|
||||||
MDMIDFilter: ptr.Uint(4),
|
|
||||||
MDMNameFilter: ptr.String("mdm_name"),
|
|
||||||
MDMEnrollmentStatusFilter: fleet.MDMEnrollStatusAutomatic,
|
|
||||||
LowDiskSpaceFilter: ptr.Int(99),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "filter with invalid status",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"status": "invalid"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "policy ID must be provided with policy response",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"policy_response": "passing"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid policy response",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"policy_id": 1,
|
|
||||||
"policy_response": "invalid"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "software title and versionID cannot be used together",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"software_title_id": 2,
|
|
||||||
"software_version_id": 1
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "os version must be provided with os name",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"os_version": "11.1"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "os name must be provided with os version",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"os_name": "macOS"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid mdm enrollment status",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"mdm_enrollment_status": "invalid"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid os settings",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"os_settings": "invalid"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid os settings disk encryption",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"os_settings_disk_encryption": "invalid"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid mdm bootstrap package",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"bootstrap_package": "invalid"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "low disk space is too low",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"low_disk_space": 0
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "low disk space is too high",
|
|
||||||
jsonBody: `{
|
|
||||||
"filter": {
|
|
||||||
"low_disk_space": 101
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expected: &fleet.HostListOptions{},
|
|
||||||
has400Error: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range cases {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
type request struct {
|
|
||||||
Somevalue string `json:"somevalue"`
|
|
||||||
Filter *fleet.HostListOptions `json:"filter"`
|
|
||||||
}
|
|
||||||
var in request
|
|
||||||
err := json.NewDecoder(strings.NewReader(tt.jsonBody)).Decode(&in)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
opts, err := validateAndPopulateHostListOptionsFilters(context.Background(), in.Filter)
|
|
||||||
if tt.has400Error {
|
|
||||||
require.Error(t, err)
|
|
||||||
var be *fleet.BadRequestError
|
|
||||||
require.ErrorAs(t, err, &be)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tt.expected, opts)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1000,7 +1000,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsFromTeam() {
|
||||||
require.NoError(t, s.ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{hosts[0].ID}))
|
require.NoError(t, s.ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{hosts[0].ID}))
|
||||||
|
|
||||||
req := deleteHostsRequest{
|
req := deleteHostsRequest{
|
||||||
Filters: &fleet.HostListOptions{TeamFilter: ptr.Uint(team1.ID)},
|
Filters: &deleteHostsFilters{TeamID: ptr.Uint(team1.ID)},
|
||||||
}
|
}
|
||||||
resp := deleteHostsResponse{}
|
resp := deleteHostsResponse{}
|
||||||
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp)
|
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp)
|
||||||
|
|
@ -1037,7 +1037,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsInLabel() {
|
||||||
require.NoError(t, s.ds.RecordLabelQueryExecutions(context.Background(), hosts[2], map[uint]*bool{label.ID: ptr.Bool(true)}, time.Now(), false))
|
require.NoError(t, s.ds.RecordLabelQueryExecutions(context.Background(), hosts[2], map[uint]*bool{label.ID: ptr.Bool(true)}, time.Now(), false))
|
||||||
|
|
||||||
req := deleteHostsRequest{
|
req := deleteHostsRequest{
|
||||||
Filters: &fleet.HostListOptions{LabelID: ptr.Uint(label.ID)},
|
Filters: &deleteHostsFilters{LabelID: ptr.Uint(label.ID)},
|
||||||
}
|
}
|
||||||
resp := deleteHostsResponse{}
|
resp := deleteHostsResponse{}
|
||||||
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp)
|
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp)
|
||||||
|
|
@ -1120,7 +1120,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsAll() {
|
||||||
|
|
||||||
// All hosts should be deleted when an empty filter is specified
|
// All hosts should be deleted when an empty filter is specified
|
||||||
req := deleteHostsRequest{
|
req := deleteHostsRequest{
|
||||||
Filters: &fleet.HostListOptions{},
|
Filters: &deleteHostsFilters{},
|
||||||
}
|
}
|
||||||
resp := deleteHostsResponse{}
|
resp := deleteHostsResponse{}
|
||||||
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp)
|
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp)
|
||||||
|
|
@ -1163,7 +1163,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsErrors() {
|
||||||
|
|
||||||
req := deleteHostsRequest{
|
req := deleteHostsRequest{
|
||||||
IDs: []uint{hosts[0].ID, hosts[1].ID},
|
IDs: []uint{hosts[0].ID, hosts[1].ID},
|
||||||
Filters: &fleet.HostListOptions{LabelID: ptr.Uint(1)},
|
Filters: &deleteHostsFilters{LabelID: ptr.Uint(1)},
|
||||||
}
|
}
|
||||||
resp := deleteHostsResponse{}
|
resp := deleteHostsResponse{}
|
||||||
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusBadRequest, &resp)
|
s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusBadRequest, &resp)
|
||||||
|
|
@ -2814,7 +2814,7 @@ func (s *integrationTestSuite) TestHostsAddToTeam() {
|
||||||
|
|
||||||
// assign host to team 2 with filter
|
// assign host to team 2 with filter
|
||||||
var addfResp addHostsToTeamByFilterResponse
|
var addfResp addHostsToTeamByFilterResponse
|
||||||
req := addHostsToTeamByFilterRequest{TeamID: &tm2.ID, Filters: &fleet.HostListOptions{}}
|
req := addHostsToTeamByFilterRequest{TeamID: &tm2.ID}
|
||||||
req.Filters.MatchQuery = hosts[2].Hostname
|
req.Filters.MatchQuery = hosts[2].Hostname
|
||||||
s.DoJSON("POST", "/api/latest/fleet/hosts/transfer/filter", req, http.StatusOK, &addfResp)
|
s.DoJSON("POST", "/api/latest/fleet/hosts/transfer/filter", req, http.StatusOK, &addfResp)
|
||||||
s.lastActivityOfTypeMatches(
|
s.lastActivityOfTypeMatches(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue