From 441e31c705f2d73fa8a2a6184e5059696cfbdf08 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Wed, 3 Jun 2026 15:07:54 -0300 Subject: [PATCH] Move targets and secret variables to server/fleet/ (#46196) Resolves #36087 (one of several PRs). ## Testing - [x] QA'd all new/changed functionality manually. ## Summary by CodeRabbit * **New Features** * Dry-run support when creating secret variables. * **Improvements** * Standardized API models for secret-variables and targets for more consistent behavior. * List secret variables now includes pagination metadata. * More consistent error reporting across secret-variables and targets APIs. * Target search/count behavior refined: pre-selected built-in labels are omitted as expected. * **Tests** * Integration tests updated to validate the new request/response behavior and target-selection logic. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/46196?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- server/fleet/api_secret_variables.go | 66 ++++++++ server/fleet/api_targets.go | 146 +++++++++++++++++ server/service/client_secret_variables.go | 4 +- server/service/client_targets.go | 4 +- server/service/handler.go | 12 +- server/service/integration_core_test.go | 94 +++++------ server/service/integration_enterprise_test.go | 20 +-- server/service/integration_mdm_ddm_test.go | 6 +- .../service/integration_mdm_profiles_test.go | 12 +- server/service/integration_mdm_test.go | 24 +-- server/service/secret_variables.go | 67 ++------ server/service/targets.go | 153 ++---------------- 12 files changed, 321 insertions(+), 287 deletions(-) create mode 100644 server/fleet/api_secret_variables.go create mode 100644 server/fleet/api_targets.go diff --git a/server/fleet/api_secret_variables.go b/server/fleet/api_secret_variables.go new file mode 100644 index 0000000000..6576d17738 --- /dev/null +++ b/server/fleet/api_secret_variables.go @@ -0,0 +1,66 @@ +package fleet + +////////////////////////////////////////////////////////////////////////////////// +// Create secret variables (spec) +////////////////////////////////////////////////////////////////////////////////// + +type CreateSecretVariablesRequest struct { + DryRun bool `json:"dry_run"` + SecretVariables []SecretVariable `json:"secrets"` +} + +type CreateSecretVariablesResponse struct { + Err error `json:"error,omitempty"` +} + +func (r CreateSecretVariablesResponse) Error() error { return r.Err } + +////////////////////////////////////////////////////////////////////////////////// +// Create secret variable +////////////////////////////////////////////////////////////////////////////////// + +type CreateSecretVariableRequest struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type CreateSecretVariableResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + + Err error `json:"error,omitempty"` +} + +func (r CreateSecretVariableResponse) Error() error { return r.Err } + +////////////////////////////////////////////////////////////////////////////////// +// List secret variables +////////////////////////////////////////////////////////////////////////////////// + +type ListSecretVariablesRequest struct { + ListOptions ListOptions `url:"list_options"` +} + +type ListSecretVariablesResponse struct { + CustomVariables []SecretVariableIdentifier `json:"custom_variables"` + Meta *PaginationMetadata `json:"meta"` + Count int `json:"count"` + + Err error `json:"error,omitempty"` +} + +func (r ListSecretVariablesResponse) Error() error { return r.Err } + +////////////////////////////////////////////////////////////////////////////////// +// Delete secret variable +////////////////////////////////////////////////////////////////////////////////// + +type DeleteSecretVariableRequest struct { + ID uint `url:"id"` +} + +type DeleteSecretVariableResponse struct { + Err error `json:"error,omitempty"` +} + +func (r DeleteSecretVariableResponse) Error() error { return r.Err } diff --git a/server/fleet/api_targets.go b/server/fleet/api_targets.go new file mode 100644 index 0000000000..d31a007bbb --- /dev/null +++ b/server/fleet/api_targets.go @@ -0,0 +1,146 @@ +package fleet + +import ( + "encoding/json" + "errors" + "time" +) + +//////////////////////////////////////////////////////////////////////////////// +// Search Targets +//////////////////////////////////////////////////////////////////////////////// + +type SearchTargetsRequest struct { + // MatchQuery is the free-text search query used to match hosts, labels, and teams. + MatchQuery string `json:"query"` + // QueryID is the ID of a saved query to run (used to determine if this is a + // query that observers can run). + QueryID *uint `json:"query_id" renameto:"report_id"` + // Selected is the list of IDs that are already selected on the caller side + // (e.g. the UI), so those are IDs that will be omitted from the returned + // payload. + Selected HostTargets `json:"selected"` +} + +type LabelSearchResult struct { + *Label + DisplayText string `json:"display_text"` + Count int `json:"count"` +} + +type TeamSearchResult struct { + *Team + DisplayText string `json:"display_text"` + Count int `json:"count"` +} + +func (t TeamSearchResult) MarshalJSON() ([]byte, error) { + if t.Team == nil { + return nil, errors.New("team search result: nil team") + } + + x := struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + Name string `json:"name"` + Description string `json:"description"` + TeamConfig + UserCount int `json:"user_count"` + Users []TeamUser `json:"users,omitempty"` + HostCount int `json:"host_count"` + Hosts []HostResponse `json:"hosts,omitempty"` + Secrets []*EnrollSecret `json:"secrets,omitempty"` + DisplayText string `json:"display_text"` + Count int `json:"count"` + }{ + ID: t.ID, + CreatedAt: t.CreatedAt, + Name: t.Name, + Description: t.Description, + TeamConfig: t.Config, + UserCount: t.UserCount, + Users: t.Users, + HostCount: t.HostCount, + Hosts: HostResponsesForHostsCheap(t.Hosts), + Secrets: t.Secrets, + DisplayText: t.DisplayText, + Count: t.Count, + } + + return json.Marshal(x) +} + +func (t *TeamSearchResult) UnmarshalJSON(b []byte) error { + var x struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + Name string `json:"name"` + Description string `json:"description"` + TeamConfig + UserCount int `json:"user_count"` + Users []TeamUser `json:"users,omitempty"` + HostCount int `json:"host_count"` + Hosts []Host `json:"hosts,omitempty"` + Secrets []*EnrollSecret `json:"secrets,omitempty"` + DisplayText string `json:"display_text"` + Count int `json:"count"` + } + + if err := json.Unmarshal(b, &x); err != nil { + return err + } + + *t = TeamSearchResult{ + Team: &Team{ + ID: x.ID, + CreatedAt: x.CreatedAt, + Name: x.Name, + Description: x.Description, + Config: x.TeamConfig, + UserCount: x.UserCount, + Users: x.Users, + HostCount: x.HostCount, + Hosts: x.Hosts, + Secrets: x.Secrets, + }, + DisplayText: x.DisplayText, + Count: x.Count, + } + + return nil +} + +type TargetsData struct { + Hosts []*HostResponse `json:"hosts"` + Labels []LabelSearchResult `json:"labels"` + Teams []TeamSearchResult `json:"teams" renameto:"fleets"` +} + +type SearchTargetsResponse struct { + Targets *TargetsData `json:"targets,omitempty"` + TargetsCount uint `json:"targets_count"` + TargetsOnline uint `json:"targets_online"` + TargetsOffline uint `json:"targets_offline"` + TargetsMissingInAction uint `json:"targets_missing_in_action"` + Err error `json:"error,omitempty"` +} + +func (r SearchTargetsResponse) Error() error { return r.Err } + +//////////////////////////////////////////////////////////////////////////////// +// Count Targets +//////////////////////////////////////////////////////////////////////////////// + +type CountTargetsRequest struct { + Selected HostTargets `json:"selected"` + QueryID *uint `json:"query_id" renameto:"report_id"` +} + +type CountTargetsResponse struct { + TargetsCount uint `json:"targets_count"` + TargetsOnline uint `json:"targets_online"` + TargetsOffline uint `json:"targets_offline"` + Err error `json:"error,omitempty"` +} + +func (r CountTargetsResponse) Error() error { return r.Err } diff --git a/server/service/client_secret_variables.go b/server/service/client_secret_variables.go index 7a35106cab..8e12355e83 100644 --- a/server/service/client_secret_variables.go +++ b/server/service/client_secret_variables.go @@ -4,10 +4,10 @@ import "github.com/fleetdm/fleet/v4/server/fleet" func (c *Client) SaveSecretVariables(secretVariables []fleet.SecretVariable, dryRun bool) error { verb, path := "PUT", "/api/latest/fleet/spec/secret_variables" - params := createSecretVariablesRequest{ + params := fleet.CreateSecretVariablesRequest{ SecretVariables: secretVariables, DryRun: dryRun, } - var responseBody createSecretVariablesResponse + var responseBody fleet.CreateSecretVariablesResponse return c.authenticatedRequest(params, verb, path, &responseBody) } diff --git a/server/service/client_targets.go b/server/service/client_targets.go index 1786afb784..202c896356 100644 --- a/server/service/client_targets.go +++ b/server/service/client_targets.go @@ -6,7 +6,7 @@ import ( // SearchTargets searches for the supplied targets in the Fleet instance. func (c *Client) SearchTargets(query string, hostIDs, labelIDs []uint) (*fleet.TargetSearchResults, error) { - req := searchTargetsRequest{ + req := fleet.SearchTargetsRequest{ MatchQuery: query, Selected: fleet.HostTargets{ LabelIDs: labelIDs, @@ -15,7 +15,7 @@ func (c *Client) SearchTargets(query string, hostIDs, labelIDs []uint) (*fleet.T }, } verb, path := "POST", "/api/latest/fleet/targets" - var responseBody searchTargetsResponse + var responseBody fleet.SearchTargetsResponse err := c.authenticatedRequest(req, verb, path, &responseBody) if err != nil { return nil, err diff --git a/server/service/handler.go b/server/service/handler.go index b75d42d42c..a8a742ddb3 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -334,8 +334,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC ue.GET("/api/_version_/fleet/email/change/{token}", changeEmailEndpoint, changeEmailRequest{}) // TODO: searchTargetsEndpoint will be removed in Fleet 5.0 - ue.POST("/api/_version_/fleet/targets", searchTargetsEndpoint, searchTargetsRequest{}) - ue.POST("/api/_version_/fleet/targets/count", countTargetsEndpoint, countTargetsRequest{}) + ue.POST("/api/_version_/fleet/targets", searchTargetsEndpoint, fleet.SearchTargetsRequest{}) + ue.POST("/api/_version_/fleet/targets/count", countTargetsEndpoint, fleet.CountTargetsRequest{}) ue.POST("/api/_version_/fleet/invites", createInviteEndpoint, createInviteRequest{}) ue.GET("/api/_version_/fleet/invites", listInvitesEndpoint, listInvitesRequest{}) @@ -583,10 +583,10 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC ue.POST("/api/_version_/fleet/autofill/policy", autofillPoliciesEndpoint, fleet.AutofillPoliciesRequest{}) // Secret variables - ue.PUT("/api/_version_/fleet/spec/secret_variables", createSecretVariablesEndpoint, createSecretVariablesRequest{}) - ue.POST("/api/_version_/fleet/custom_variables", createSecretVariableEndpoint, createSecretVariableRequest{}) - ue.GET("/api/_version_/fleet/custom_variables", listSecretVariablesEndpoint, listSecretVariablesRequest{}) - ue.DELETE("/api/_version_/fleet/custom_variables/{id:[0-9]+}", deleteSecretVariableEndpoint, deleteSecretVariableRequest{}) + ue.PUT("/api/_version_/fleet/spec/secret_variables", createSecretVariablesEndpoint, fleet.CreateSecretVariablesRequest{}) + ue.POST("/api/_version_/fleet/custom_variables", createSecretVariableEndpoint, fleet.CreateSecretVariableRequest{}) + ue.GET("/api/_version_/fleet/custom_variables", listSecretVariablesEndpoint, fleet.ListSecretVariablesRequest{}) + ue.DELETE("/api/_version_/fleet/custom_variables/{id:[0-9]+}", deleteSecretVariableEndpoint, fleet.DeleteSecretVariableRequest{}) // API end-points ue.GET("/api/_version_/fleet/rest_api", listAPIEndpointsEndpoint, listAPIEndpointsRequest{}) diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index e0be131b01..cc5e3c310d 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -9540,8 +9540,8 @@ func (s *integrationTestSuite) TestSearchTargets() { require.Len(t, lblMap, len(builtinNames)) // no search criteria - var searchResp searchTargetsResponse - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{}, http.StatusOK, &searchResp) + var searchResp fleet.SearchTargetsResponse + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{}, http.StatusOK, &searchResp) require.Equal(t, uint(0), searchResp.TargetsCount) require.Len(t, searchResp.Targets.Hosts, len(hosts)) // the HostTargets.HostIDs are actually host IDs to *omit* from the search require.Len(t, searchResp.Targets.Labels, len(lblMap)) @@ -9552,22 +9552,22 @@ func (s *integrationTestSuite) TestSearchTargets() { lblIDs = append(lblIDs, labelID) } - searchResp = searchTargetsResponse{} - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{Selected: fleet.HostTargets{LabelIDs: lblIDs}}, http.StatusOK, &searchResp) + searchResp = fleet.SearchTargetsResponse{} + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{Selected: fleet.HostTargets{LabelIDs: lblIDs}}, http.StatusOK, &searchResp) require.Equal(t, uint(0), searchResp.TargetsCount) require.Len(t, searchResp.Targets.Hosts, len(hosts)) // no omitted host id require.Len(t, searchResp.Targets.Labels, 0) // All built-in labels have been omitted (pre-selected) require.Len(t, searchResp.Targets.Teams, 0) - searchResp = searchTargetsResponse{} - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{Selected: fleet.HostTargets{HostIDs: []uint{hosts[1].ID}}}, http.StatusOK, &searchResp) + searchResp = fleet.SearchTargetsResponse{} + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{Selected: fleet.HostTargets{HostIDs: []uint{hosts[1].ID}}}, http.StatusOK, &searchResp) require.Equal(t, uint(1), searchResp.TargetsCount) require.Len(t, searchResp.Targets.Hosts, len(hosts)-1) // one omitted host id require.Len(t, searchResp.Targets.Labels, len(lblMap)) // labels have not been omitted require.Len(t, searchResp.Targets.Teams, 0) - searchResp = searchTargetsResponse{} - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{MatchQuery: "foo.local1"}, http.StatusOK, &searchResp) + searchResp = fleet.SearchTargetsResponse{} + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{MatchQuery: "foo.local1"}, http.StatusOK, &searchResp) require.Equal(t, uint(0), searchResp.TargetsCount) require.Len(t, searchResp.Targets.Hosts, 1) require.Len(t, searchResp.Targets.Labels, 1) // with a match query, only matching label names and "All Hosts" can be returned (here, only all hosts) @@ -9673,12 +9673,12 @@ func (s *integrationTestSuite) TestCountTargets() { err = s.ds.AddHostsToTeam(context.Background(), fleet.NewAddHostsToTeamParams(ptr.Uint(team.ID), []uint{hostIDs[0]})) require.NoError(t, err) - var countResp countTargetsResponse + var countResp fleet.CountTargetsResponse // sleep to reduce flake in last seen time so that online/offline counts can be tested time.Sleep(1 * time.Second) // none selected - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{}, http.StatusOK, &countResp) + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{}, http.StatusOK, &countResp) require.Equal(t, uint(0), countResp.TargetsCount) require.Equal(t, uint(0), countResp.TargetsOnline) require.Equal(t, uint(0), countResp.TargetsOffline) @@ -9688,23 +9688,23 @@ func (s *integrationTestSuite) TestCountTargets() { lblIDs = append(lblIDs, labelID) } // all hosts label selected - countResp = countTargetsResponse{} - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{Selected: fleet.HostTargets{LabelIDs: lblIDs}}, http.StatusOK, &countResp) + countResp = fleet.CountTargetsResponse{} + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{Selected: fleet.HostTargets{LabelIDs: lblIDs}}, http.StatusOK, &countResp) require.Equal(t, uint(3), countResp.TargetsCount) require.Equal(t, uint(1), countResp.TargetsOnline) require.Equal(t, uint(2), countResp.TargetsOffline) // team selected - countResp = countTargetsResponse{} - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{Selected: fleet.HostTargets{TeamIDs: []uint{team.ID}}}, http.StatusOK, &countResp) + countResp = fleet.CountTargetsResponse{} + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{Selected: fleet.HostTargets{TeamIDs: []uint{team.ID}}}, http.StatusOK, &countResp) require.Equal(t, uint(1), countResp.TargetsCount) require.Equal(t, uint(1), countResp.TargetsOnline) require.Equal(t, uint(0), countResp.TargetsOffline) // 'No team' selected - countResp = countTargetsResponse{} + countResp = fleet.CountTargetsResponse{} s.DoJSON( - "POST", "/api/latest/fleet/targets/count", countTargetsRequest{Selected: fleet.HostTargets{TeamIDs: []uint{0}}}, + "POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{Selected: fleet.HostTargets{TeamIDs: []uint{0}}}, http.StatusOK, &countResp, ) assert.Equal(t, uint(2), countResp.TargetsCount) @@ -9712,8 +9712,8 @@ func (s *integrationTestSuite) TestCountTargets() { assert.Equal(t, uint(2), countResp.TargetsOffline) // host id selected - countResp = countTargetsResponse{} - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{Selected: fleet.HostTargets{HostIDs: []uint{hosts[1].ID}}}, http.StatusOK, &countResp) + countResp = fleet.CountTargetsResponse{} + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{Selected: fleet.HostTargets{HostIDs: []uint{hosts[1].ID}}}, http.StatusOK, &countResp) require.Equal(t, uint(1), countResp.TargetsCount) require.Equal(t, uint(0), countResp.TargetsOnline) require.Equal(t, uint(1), countResp.TargetsOffline) @@ -15075,12 +15075,12 @@ func (s *integrationTestSuite) TestSecretVariablesGitOps() { s.setTokenForTest(t, "gitops1@example.com", test.GoodPassword) // Empty request - req := createSecretVariablesRequest{} - var resp createSecretVariablesResponse + req := fleet.CreateSecretVariablesRequest{} + var resp fleet.CreateSecretVariablesResponse s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &resp) // Secret variable name too long - req = createSecretVariablesRequest{ + req = fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: strings.Repeat("a", 256), @@ -15092,7 +15092,7 @@ func (s *integrationTestSuite) TestSecretVariablesGitOps() { assertBodyContains(t, httpResp, "secret variable name is too long") // Secret variable name empty - req = createSecretVariablesRequest{ + req = fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: " ", @@ -15104,7 +15104,7 @@ func (s *integrationTestSuite) TestSecretVariablesGitOps() { assertBodyContains(t, httpResp, "secret variable name cannot be empty") validName := strings.Repeat("G", 255) - req = createSecretVariablesRequest{ + req = fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_" + validName, @@ -15134,8 +15134,8 @@ func (s *integrationTestSuite) TestSecretVariables() { ctx := context.Background() // Create a single secret variable. - var csvr createSecretVariableResponse - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + var csvr fleet.CreateSecretVariableResponse + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "NAME1", Value: "value1", }, http.StatusOK, &csvr) @@ -15143,8 +15143,8 @@ func (s *integrationTestSuite) TestSecretVariables() { require.NotZero(t, firstVariableID) // List (no-filtering). - var lsvr listSecretVariablesResponse - s.DoJSON("GET", "/api/latest/fleet/custom_variables", listSecretVariablesRequest{}, http.StatusOK, &lsvr) + var lsvr fleet.ListSecretVariablesResponse + s.DoJSON("GET", "/api/latest/fleet/custom_variables", fleet.ListSecretVariablesRequest{}, http.StatusOK, &lsvr) require.Equal(t, lsvr.Count, 1) require.Len(t, lsvr.CustomVariables, 1) require.NotZero(t, lsvr.CustomVariables[0].ID) @@ -15160,21 +15160,21 @@ func (s *integrationTestSuite) TestSecretVariables() { require.NotZero(t, secretVariables[0].UpdatedAt) // Creating the same variable should fail with conflict. - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "NAME1", Value: "value1", }, http.StatusConflict, &csvr) // Creating a variable with invalid name should fail with 422. - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "lowercase", Value: "foobar", }, http.StatusUnprocessableEntity, &csvr) - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "", Value: "foobar", }, http.StatusUnprocessableEntity, &csvr) - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: strings.Repeat("HA ", 255/3+1), Value: "foobar", }, http.StatusUnprocessableEntity, &csvr) @@ -15183,7 +15183,7 @@ func (s *integrationTestSuite) TestSecretVariables() { defer func() { testSetEmptyPrivateKey = false }() - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "NAME2", Value: "foobar", }, http.StatusBadRequest, &csvr) @@ -15191,13 +15191,13 @@ func (s *integrationTestSuite) TestSecretVariables() { testSetEmptyPrivateKey = false // Creating a variable with empty value should fail with 422. - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "ANOTHER_NAME", Value: "", }, http.StatusUnprocessableEntity, &csvr) // Creating a second variable. - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "ANOTHER_NAME", Value: "value2", }, http.StatusOK, &csvr) @@ -15205,7 +15205,7 @@ func (s *integrationTestSuite) TestSecretVariables() { require.NotZero(t, secondVariableID) // List (no-filtering) with pagination (first page). - lsvr = listSecretVariablesResponse{} + lsvr = fleet.ListSecretVariablesResponse{} s.DoJSON("GET", "/api/latest/fleet/custom_variables", nil, http.StatusOK, &lsvr, "per_page", "1", "page", "0") require.Equal(t, 2, lsvr.Count) require.NotNil(t, lsvr.Meta) @@ -15216,7 +15216,7 @@ func (s *integrationTestSuite) TestSecretVariables() { require.Equal(t, "ANOTHER_NAME", lsvr.CustomVariables[0].Name) require.NotZero(t, lsvr.CustomVariables[0].UpdatedAt) // List (no-filtering) with pagination (second page). - lsvr = listSecretVariablesResponse{} + lsvr = fleet.ListSecretVariablesResponse{} s.DoJSON("GET", "/api/latest/fleet/custom_variables", nil, http.StatusOK, &lsvr, "per_page", "1", "page", "1") require.Equal(t, 2, lsvr.Count) require.NotNil(t, lsvr.Meta) @@ -15228,7 +15228,7 @@ func (s *integrationTestSuite) TestSecretVariables() { require.NotZero(t, lsvr.CustomVariables[0].UpdatedAt) // List (no-filtering) with pagination (one page, two secrets). // Must be ordered alphabetically. - lsvr = listSecretVariablesResponse{} + lsvr = fleet.ListSecretVariablesResponse{} s.DoJSON("GET", "/api/latest/fleet/custom_variables", nil, http.StatusOK, &lsvr, "per_page", "20", "page", "0") require.Equal(t, 2, lsvr.Count) require.NotNil(t, lsvr.Meta) @@ -15243,14 +15243,14 @@ func (s *integrationTestSuite) TestSecretVariables() { require.NotZero(t, lsvr.CustomVariables[1].UpdatedAt) // Test deletion of non-existent ID - var dsvr deleteSecretVariableResponse + var dsvr fleet.DeleteSecretVariableResponse s.DoJSON("DELETE", "/api/latest/fleet/custom_variables/999", nil, http.StatusNotFound, &dsvr) s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/custom_variables/%d", firstVariableID), nil, http.StatusOK, &dsvr) s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/custom_variables/%d", secondVariableID), nil, http.StatusOK, &dsvr) // List after deletions should be empty. - lsvr = listSecretVariablesResponse{} + lsvr = fleet.ListSecretVariablesResponse{} s.DoJSON("GET", "/api/latest/fleet/custom_variables", nil, http.StatusOK, &lsvr) require.Equal(t, 0, lsvr.Count) require.Empty(t, lsvr.CustomVariables) @@ -15266,8 +15266,8 @@ func (s *integrationTestSuite) TestSecretVariablesInUse() { require.NoError(t, err) // Create a single secret variable. - var csvr createSecretVariableResponse - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + var csvr fleet.CreateSecretVariableResponse + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "NAME1", Value: "value1", }, http.StatusOK, &csvr) @@ -15350,7 +15350,7 @@ func (s *integrationTestSuite) TestSecretVariablesInUse() { require.NoError(t, err) // Finally, delete now should work. - var dsvr deleteSecretVariableResponse + var dsvr fleet.DeleteSecretVariableResponse s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/custom_variables/%d", firstVariableID), nil, http.StatusOK, &dsvr) } @@ -15359,8 +15359,8 @@ func (s *integrationTestSuite) TestSecretVariablesPermissions() { ctx := context.Background() // Create a single secret variable. - var csvr createSecretVariableResponse - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + var csvr fleet.CreateSecretVariableResponse + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "NAME1", Value: "foobar", }, http.StatusOK, &csvr) @@ -15376,14 +15376,14 @@ func (s *integrationTestSuite) TestSecretVariablesPermissions() { require.NoError(t, err) s.setTokenForTest(t, "observer@example.com", test.GoodPassword) - s.DoJSON("POST", "/api/latest/fleet/custom_variables", createSecretVariableRequest{ + s.DoJSON("POST", "/api/latest/fleet/custom_variables", fleet.CreateSecretVariableRequest{ Name: "NAME1", Value: "foobar", }, http.StatusForbidden, &csvr) // List (no-filtering) should work for non-admins. - var lsvr listSecretVariablesResponse - s.DoJSON("GET", "/api/latest/fleet/custom_variables", listSecretVariablesRequest{}, http.StatusOK, &lsvr) + var lsvr fleet.ListSecretVariablesResponse + s.DoJSON("GET", "/api/latest/fleet/custom_variables", fleet.ListSecretVariablesRequest{}, http.StatusOK, &lsvr) require.Equal(t, lsvr.Count, 1) require.Len(t, lsvr.CustomVariables, 1) require.NotZero(t, lsvr.CustomVariables[0].ID) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index a103741125..2bef23e839 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -7358,20 +7358,20 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() { s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/carves/%d", carveID), fleet.GetCarveRequest{}, http.StatusForbidden, &fleet.GetCarveResponse{}) // Attempt to search hosts, should fail. - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{ MatchQuery: "foo", QueryID: &q1.ID, - }, http.StatusForbidden, &searchTargetsResponse{}) + }, http.StatusForbidden, &fleet.SearchTargetsResponse{}) // Attempt to count target hosts, should fail. - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{ Selected: fleet.HostTargets{ HostIDs: []uint{h1.ID}, LabelIDs: []uint{clr.Label.ID}, TeamIDs: []uint{t1.ID}, }, QueryID: &q1.ID, - }, http.StatusForbidden, &countTargetsResponse{}) + }, http.StatusForbidden, &fleet.CountTargetsResponse{}) // // Start running permission tests with user gitops2 (which is a GitOps use for team t1). @@ -7594,20 +7594,20 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() { }, http.StatusForbidden, &teamResponse{}) // Attempt to search hosts, should fail. - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{ MatchQuery: "foo", QueryID: &q1.ID, - }, http.StatusForbidden, &searchTargetsResponse{}) + }, http.StatusForbidden, &fleet.SearchTargetsResponse{}) // Attempt to count target hosts, should fail. - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{ Selected: fleet.HostTargets{ HostIDs: []uint{h1.ID}, LabelIDs: []uint{clr.Label.ID}, TeamIDs: []uint{t1.ID}, }, QueryID: &q1.ID, - }, http.StatusForbidden, &countTargetsResponse{}) + }, http.StatusForbidden, &fleet.CountTargetsResponse{}) // Listing software titles for the team it owns is allowed. s.DoJSON("GET", "/api/latest/fleet/software/titles", listSoftwareTitlesRequest{}, http.StatusOK, &listSoftwareTitlesResponse{}, "team_id", fmt.Sprint(t1.ID)) @@ -7684,7 +7684,7 @@ func (s *integrationEnterpriseTestSuite) TestRunHostScript() { // Upload a valid secret secretValue := "abc123" - req := createSecretVariablesRequest{ + req := fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_TEST_RUN_HOST_SCRIPT", @@ -7692,7 +7692,7 @@ func (s *integrationEnterpriseTestSuite) TestRunHostScript() { }, }, } - secretResp := createSecretVariablesResponse{} + secretResp := fleet.CreateSecretVariablesResponse{} s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &secretResp) // create a valid script execution request diff --git a/server/service/integration_mdm_ddm_test.go b/server/service/integration_mdm_ddm_test.go index 9ba07a52f4..ebcf07a383 100644 --- a/server/service/integration_mdm_ddm_test.go +++ b/server/service/integration_mdm_ddm_test.go @@ -525,7 +525,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMSecretVariables() { require.Empty(t, resp.Profiles) // Add secrets to server - req := createSecretVariablesRequest{ + req := fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_BASH", @@ -537,7 +537,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMSecretVariables() { }, }, } - secretResp := createSecretVariablesResponse{} + secretResp := fleet.CreateSecretVariablesResponse{} s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &secretResp) // Now real run @@ -654,7 +654,7 @@ WHERE name = ?` // Change the secrets. myBash = "my.new.bash" - req = createSecretVariablesRequest{ + req = fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_BASH", diff --git a/server/service/integration_mdm_profiles_test.go b/server/service/integration_mdm_profiles_test.go index d1013224c8..701811f6fb 100644 --- a/server/service/integration_mdm_profiles_test.go +++ b/server/service/integration_mdm_profiles_test.go @@ -243,7 +243,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { secretIdentifier := "secret-identifier-1" secretType := "secret.type.1" secretProfile := string(mobileconfigForTest("NS1", "IS1")) - req := createSecretVariablesRequest{ + req := fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_IDENTIFIER", @@ -259,7 +259,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { }, }, } - secretResp := createSecretVariablesResponse{} + secretResp := fleet.CreateSecretVariablesResponse{} s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &secretResp) // set new team profiles (delete + addition) @@ -305,7 +305,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { // Change the secret variable and upload the profiles again. We should see the profile with updated secret installed. secretType = "new.secret.type.1" - req = createSecretVariablesRequest{ + req = fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_IDENTIFIER", @@ -379,7 +379,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { // Change the secret variable and upload the profiles again. We should see the profile with updated secret installed. secretType = "new2.secret.type.1" - req = createSecretVariablesRequest{ + req = fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_IDENTIFIER", @@ -6668,7 +6668,7 @@ func (s *integrationMDMTestSuite) testSecretVariablesUpload(newProfileBytes func assertBodyContains(t, res, `Secret variable \"$FLEET_SECRET_BASH\" missing`) // Add secret(s) to server - req := createSecretVariablesRequest{ + req := fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_BASH", @@ -6680,7 +6680,7 @@ func (s *integrationMDMTestSuite) testSecretVariablesUpload(newProfileBytes func }, }, } - secretResp := createSecretVariablesResponse{} + secretResp := fleet.CreateSecretVariablesResponse{} s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &secretResp) res = s.DoRawWithHeaders("POST", "/api/latest/fleet/configuration_profiles", body.Bytes(), http.StatusOK, headers) var resp newMDMConfigProfileResponse diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index e5536c3285..8297244a8e 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -3979,7 +3979,7 @@ func (s *integrationMDMTestSuite) TestEnqueueMDMCommandWithSecret() { // Load secret(s) secretValue := "*abc123*" - req := createSecretVariablesRequest{ + req := fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_VALUE", @@ -3987,7 +3987,7 @@ func (s *integrationMDMTestSuite) TestEnqueueMDMCommandWithSecret() { }, }, } - secretResp := createSecretVariablesResponse{} + secretResp := fleet.CreateSecretVariablesResponse{} s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &secretResp) // call with enrolled host UUID @@ -9154,7 +9154,7 @@ func (s *integrationMDMTestSuite) TestWindowsMDMCommandWithSecret() { orbitHost, d := createWindowsHostThenEnrollMDM(s.ds, s.server.URL, t) secretValue := "abcd1234" - req := createSecretVariablesRequest{ + req := fleet.CreateSecretVariablesRequest{ SecretVariables: []fleet.SecretVariable{ { Name: "FLEET_SECRET_DATA", @@ -9162,7 +9162,7 @@ func (s *integrationMDMTestSuite) TestWindowsMDMCommandWithSecret() { }, }, } - secretResp := createSecretVariablesResponse{} + secretResp := fleet.CreateSecretVariablesResponse{} s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &secretResp) cmdOneUUID := uuid.New().String() @@ -23005,20 +23005,20 @@ func (s *integrationMDMTestSuite) TestTechnicianPermissions() { s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/carves/%d", carveID), fleet.GetCarveRequest{}, http.StatusForbidden, &fleet.GetCarveResponse{}) // Attempt to search hosts, should allow. - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{ MatchQuery: "foo", QueryID: &q1.ID, - }, http.StatusOK, &searchTargetsResponse{}) + }, http.StatusOK, &fleet.SearchTargetsResponse{}) // Attempt to count target hosts, should allow. - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{ Selected: fleet.HostTargets{ HostIDs: []uint{h1.ID}, LabelIDs: []uint{clr.Label.ID}, TeamIDs: []uint{t1.ID}, }, QueryID: &q1.ID, - }, http.StatusOK, &countTargetsResponse{}) + }, http.StatusOK, &fleet.CountTargetsResponse{}) checkDownloadResponse := func(t *testing.T, r *http.Response, expectedFilename string) { require.Equal(t, "application/octet-stream", r.Header.Get("Content-Type")) @@ -23356,20 +23356,20 @@ func (s *integrationMDMTestSuite) TestTechnicianPermissions() { }, http.StatusForbidden, &teamResponse{}) // Attempt to search hosts, should allow. - s.DoJSON("POST", "/api/latest/fleet/targets", searchTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets", fleet.SearchTargetsRequest{ MatchQuery: "foo", QueryID: &q1.ID, - }, http.StatusOK, &searchTargetsResponse{}) + }, http.StatusOK, &fleet.SearchTargetsResponse{}) // Attempt to count target hosts, should allow. - s.DoJSON("POST", "/api/latest/fleet/targets/count", countTargetsRequest{ + s.DoJSON("POST", "/api/latest/fleet/targets/count", fleet.CountTargetsRequest{ Selected: fleet.HostTargets{ HostIDs: []uint{h1.ID}, LabelIDs: []uint{clr.Label.ID}, TeamIDs: []uint{t1.ID}, }, QueryID: &q1.ID, - }, http.StatusOK, &countTargetsResponse{}) + }, http.StatusOK, &fleet.CountTargetsResponse{}) // Attempt to download installer from t1, should allow. tokenResp = getSoftwareInstallerTokenResponse{} diff --git a/server/service/secret_variables.go b/server/service/secret_variables.go index 6cd38fbde0..159c61e383 100644 --- a/server/service/secret_variables.go +++ b/server/service/secret_variables.go @@ -19,21 +19,10 @@ const ( // Create secret variables (spec) ////////////////////////////////////////////////////////////////////////////////// -type createSecretVariablesRequest struct { - DryRun bool `json:"dry_run"` - SecretVariables []fleet.SecretVariable `json:"secrets"` -} - -type createSecretVariablesResponse struct { - Err error `json:"error,omitempty"` -} - -func (r createSecretVariablesResponse) Error() error { return r.Err } - func createSecretVariablesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) { - req := request.(*createSecretVariablesRequest) + req := request.(*fleet.CreateSecretVariablesRequest) err := svc.CreateSecretVariables(ctx, req.SecretVariables, req.DryRun) - return createSecretVariablesResponse{Err: err}, nil + return fleet.CreateSecretVariablesResponse{Err: err}, nil } func (svc *Service) CreateSecretVariables(ctx context.Context, secretVariables []fleet.SecretVariable, dryRun bool) error { @@ -77,27 +66,13 @@ func (svc *Service) CreateSecretVariables(ctx context.Context, secretVariables [ // Create secret variable ////////////////////////////////////////////////////////////////////////////////// -type createSecretVariableRequest struct { - Name string `json:"name"` - Value string `json:"value"` -} - -type createSecretVariableResponse struct { - ID uint `json:"id"` - Name string `json:"name"` - - Err error `json:"error,omitempty"` -} - -func (r createSecretVariableResponse) Error() error { return r.Err } - func createSecretVariableEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) { - req := request.(*createSecretVariableRequest) + req := request.(*fleet.CreateSecretVariableRequest) id, err := svc.CreateSecretVariable(ctx, req.Name, req.Value) if err != nil { - return createSecretVariableResponse{Err: err}, nil + return fleet.CreateSecretVariableResponse{Err: err}, nil } - return createSecretVariableResponse{ + return fleet.CreateSecretVariableResponse{ ID: id, Name: req.Name, }, nil @@ -150,24 +125,10 @@ func (svc *Service) CreateSecretVariable(ctx context.Context, name string, value // List secret variables ////////////////////////////////////////////////////////////////////////////////// -type listSecretVariablesRequest struct { - ListOptions fleet.ListOptions `url:"list_options"` -} - -type listSecretVariablesResponse struct { - CustomVariables []fleet.SecretVariableIdentifier `json:"custom_variables"` - Meta *fleet.PaginationMetadata `json:"meta"` - Count int `json:"count"` - - Err error `json:"error,omitempty"` -} - -func (r listSecretVariablesResponse) Error() error { return r.Err } - func listSecretVariablesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) { - req := request.(*listSecretVariablesRequest) + req := request.(*fleet.ListSecretVariablesRequest) secretVariables, meta, count, err := svc.ListSecretVariables(ctx, req.ListOptions) - return listSecretVariablesResponse{ + return fleet.ListSecretVariablesResponse{ CustomVariables: secretVariables, Meta: meta, Count: count, @@ -210,20 +171,10 @@ func (svc *Service) ListSecretVariables( // Delete secret variable ////////////////////////////////////////////////////////////////////////////////// -type deleteSecretVariableRequest struct { - ID uint `url:"id"` -} - -type deleteSecretVariableResponse struct { - Err error `json:"error,omitempty"` -} - -func (r deleteSecretVariableResponse) Error() error { return r.Err } - func deleteSecretVariableEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) { - req := request.(*deleteSecretVariableRequest) + req := request.(*fleet.DeleteSecretVariableRequest) err := svc.DeleteSecretVariable(ctx, req.ID) - return deleteSecretVariableResponse{ + return fleet.DeleteSecretVariableResponse{ Err: err, }, nil } diff --git a/server/service/targets.go b/server/service/targets.go index d57891cb9a..fc5d642e0d 100644 --- a/server/service/targets.go +++ b/server/service/targets.go @@ -2,8 +2,6 @@ package service import ( "context" - "encoding/json" - "time" "github.com/fleetdm/fleet/v4/server/contexts/viewer" "github.com/fleetdm/fleet/v4/server/fleet" @@ -13,131 +11,18 @@ import ( // Search Targets //////////////////////////////////////////////////////////////////////////////// -type searchTargetsRequest struct { - // MatchQuery is the query SQL - MatchQuery string `json:"query"` - // QueryID is the ID of a saved query to run (used to determine if this is a - // query that observers can run). - QueryID *uint `json:"query_id" renameto:"report_id"` - // Selected is the list of IDs that are already selected on the caller side - // (e.g. the UI), so those are IDs that will be omitted from the returned - // payload. - Selected fleet.HostTargets `json:"selected"` -} - -type labelSearchResult struct { - *fleet.Label - DisplayText string `json:"display_text"` - Count int `json:"count"` -} - -type teamSearchResult struct { - *fleet.Team - DisplayText string `json:"display_text"` - Count int `json:"count"` -} - -func (t teamSearchResult) MarshalJSON() ([]byte, error) { - x := struct { - ID uint `json:"id"` - CreatedAt time.Time `json:"created_at"` - Name string `json:"name"` - Description string `json:"description"` - fleet.TeamConfig - UserCount int `json:"user_count"` - Users []fleet.TeamUser `json:"users,omitempty"` - HostCount int `json:"host_count"` - Hosts []fleet.HostResponse `json:"hosts,omitempty"` - Secrets []*fleet.EnrollSecret `json:"secrets,omitempty"` - DisplayText string `json:"display_text"` - Count int `json:"count"` - }{ - ID: t.ID, - CreatedAt: t.CreatedAt, - Name: t.Name, - Description: t.Description, - TeamConfig: t.Config, - UserCount: t.UserCount, - Users: t.Users, - HostCount: t.HostCount, - Hosts: fleet.HostResponsesForHostsCheap(t.Hosts), - Secrets: t.Secrets, - DisplayText: t.DisplayText, - Count: t.Count, - } - - return json.Marshal(x) -} - -func (t *teamSearchResult) UnmarshalJSON(b []byte) error { - var x struct { - ID uint `json:"id"` - CreatedAt time.Time `json:"created_at"` - Name string `json:"name"` - Description string `json:"description"` - fleet.TeamConfig - UserCount int `json:"user_count"` - Users []fleet.TeamUser `json:"users,omitempty"` - HostCount int `json:"host_count"` - Hosts []fleet.Host `json:"hosts,omitempty"` - Secrets []*fleet.EnrollSecret `json:"secrets,omitempty"` - DisplayText string `json:"display_text"` - Count int `json:"count"` - } - - if err := json.Unmarshal(b, &x); err != nil { - return err - } - - *t = teamSearchResult{ - Team: &fleet.Team{ - ID: x.ID, - CreatedAt: x.CreatedAt, - Name: x.Name, - Description: x.Description, - Config: x.TeamConfig, - UserCount: x.UserCount, - Users: x.Users, - HostCount: x.HostCount, - Hosts: x.Hosts, - Secrets: x.Secrets, - }, - DisplayText: x.DisplayText, - Count: x.Count, - } - - return nil -} - -type targetsData struct { - Hosts []*fleet.HostResponse `json:"hosts"` - Labels []labelSearchResult `json:"labels"` - Teams []teamSearchResult `json:"teams" renameto:"fleets"` -} - -type searchTargetsResponse struct { - Targets *targetsData `json:"targets,omitempty"` - TargetsCount uint `json:"targets_count"` - TargetsOnline uint `json:"targets_online"` - TargetsOffline uint `json:"targets_offline"` - TargetsMissingInAction uint `json:"targets_missing_in_action"` - Err error `json:"error,omitempty"` -} - -func (r searchTargetsResponse) Error() error { return r.Err } - func searchTargetsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) { - req := request.(*searchTargetsRequest) + req := request.(*fleet.SearchTargetsRequest) results, err := svc.SearchTargets(ctx, req.MatchQuery, req.QueryID, req.Selected) if err != nil { - return searchTargetsResponse{Err: err}, nil + return fleet.SearchTargetsResponse{Err: err}, nil } - targets := &targetsData{ + targets := &fleet.TargetsData{ Hosts: []*fleet.HostResponse{}, - Labels: []labelSearchResult{}, - Teams: []teamSearchResult{}, + Labels: []fleet.LabelSearchResult{}, + Teams: []fleet.TeamSearchResult{}, } for _, host := range results.Hosts { @@ -146,7 +31,7 @@ func searchTargetsEndpoint(ctx context.Context, request interface{}, svc fleet.S for _, label := range results.Labels { targets.Labels = append(targets.Labels, - labelSearchResult{ + fleet.LabelSearchResult{ Label: label, DisplayText: label.Name, Count: label.HostCount, @@ -156,7 +41,7 @@ func searchTargetsEndpoint(ctx context.Context, request interface{}, svc fleet.S for _, team := range results.Teams { targets.Teams = append(targets.Teams, - teamSearchResult{ + fleet.TeamSearchResult{ Team: team, DisplayText: team.Name, Count: team.HostCount, @@ -166,10 +51,10 @@ func searchTargetsEndpoint(ctx context.Context, request interface{}, svc fleet.S metrics, err := svc.CountHostsInTargets(ctx, req.QueryID, req.Selected) if err != nil { - return searchTargetsResponse{Err: err}, nil + return fleet.SearchTargetsResponse{Err: err}, nil } - return searchTargetsResponse{ + return fleet.SearchTargetsResponse{ Targets: targets, TargetsCount: metrics.TotalHosts, TargetsOnline: metrics.OnlineHosts, @@ -258,29 +143,15 @@ func (svc *Service) CountHostsInTargets(ctx context.Context, queryID *uint, targ return &metrics, nil } -type countTargetsRequest struct { - Selected fleet.HostTargets `json:"selected"` - QueryID *uint `json:"query_id" renameto:"report_id"` -} - -type countTargetsResponse struct { - TargetsCount uint `json:"targets_count"` - TargetsOnline uint `json:"targets_online"` - TargetsOffline uint `json:"targets_offline"` - Err error `json:"error,omitempty"` -} - -func (r countTargetsResponse) Error() error { return r.Err } - func countTargetsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) { - req := request.(*countTargetsRequest) + req := request.(*fleet.CountTargetsRequest) counts, err := svc.CountHostsInTargets(ctx, req.QueryID, req.Selected) if err != nil { - return searchTargetsResponse{Err: err}, nil + return fleet.CountTargetsResponse{Err: err}, nil } - return countTargetsResponse{ + return fleet.CountTargetsResponse{ TargetsCount: counts.TotalHosts, TargetsOnline: counts.OnlineHosts, TargetsOffline: counts.OfflineHosts,