From 48b992e268d69665f12190216f9b5ccd4063ba52 Mon Sep 17 00:00:00 2001 From: Konstantin Sykulev Date: Wed, 13 Nov 2024 17:22:51 -0600 Subject: [PATCH] Modify status code for software batch endpoint (#23711) When using the `fleet/software/batch` endpoint, due to its async nature, it should return a 202 (Accepted) rather than a 200 (Ok). https://github.com/fleetdm/fleet/issues/23492 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Added/updated tests - [x] Manual QA for all new/changed functionality --- changes/23492-software-batch-status-code | 1 + docs/Contributing/API-for-contributors.md | 2 +- server/service/integration_enterprise_test.go | 36 +++++++++---------- server/service/software_installers.go | 1 + 4 files changed, 21 insertions(+), 19 deletions(-) create mode 100644 changes/23492-software-batch-status-code diff --git a/changes/23492-software-batch-status-code b/changes/23492-software-batch-status-code new file mode 100644 index 000000000000..9ab51770d9a4 --- /dev/null +++ b/changes/23492-software-batch-status-code @@ -0,0 +1 @@ +* Updated software batch endpoint status code from 200 (OK) to 202 (Accepted) \ No newline at end of file diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md index 1a375bd0816d..86eb77ae82df 100644 --- a/docs/Contributing/API-for-contributors.md +++ b/docs/Contributing/API-for-contributors.md @@ -3516,7 +3516,7 @@ This endpoint is asynchronous, meaning it will start a background process to dow ##### Default response -`Status: 200` +`Status: 202` ```json { "request_uuid": "ec23c7b6-c336-4109-b89d-6afd859659b4", diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 51ac57ba6877..533e45a402dc 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -10934,7 +10934,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { {URL: srv.URL + "/not_found.pkg"}, } var batchResponse batchSetSoftwareInstallersResponse - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) message := waitBatchSetSoftwareInstallersFailed(t, s, tm.Name, batchResponse.RequestUUID) require.NotEmpty(t, message) require.Contains(t, message, fmt.Sprintf("validation failed: software.url Couldn't edit software. URL (\"%s/not_found.pkg\") returned \"Not Found\". Please make sure that URLs are reachable from your Fleet server.", srv.URL)) @@ -10944,7 +10944,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { softwareToInstall = []fleet.SoftwareInstallerPayload{ {URL: rubyURL}, } - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages := waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -10975,7 +10975,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { }) // same payload doesn't modify anything - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -10989,7 +10989,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { // setting self-service to true updates the software title metadata softwareToInstall[0].SelfService = true - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11004,7 +11004,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { // empty payload cleans the software items softwareToInstall = []fleet.SoftwareInstallerPayload{} - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Empty(t, packages) titlesResp = listSoftwareTitlesResponse{} @@ -11019,7 +11019,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { softwareToInstall = []fleet.SoftwareInstallerPayload{ {URL: rubyURL}, } - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse) packages = waitBatchSetSoftwareInstallersCompleted(t, s, "", batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11033,7 +11033,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { require.Len(t, titlesResp.SoftwareTitles, 1) // same payload doesn't modify anything - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse) packages = waitBatchSetSoftwareInstallersCompleted(t, s, "", batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11045,7 +11045,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { // setting self-service to true updates the software title metadata softwareToInstall[0].SelfService = true - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse) packages = waitBatchSetSoftwareInstallersCompleted(t, s, "", batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11058,7 +11058,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { // empty payload cleans the software items softwareToInstall = []fleet.SoftwareInstallerPayload{} - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse) packages = waitBatchSetSoftwareInstallersCompleted(t, s, "", batchResponse.RequestUUID) require.Empty(t, packages) titlesResp = listSoftwareTitlesResponse{} @@ -11133,7 +11133,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec {URL: srv.URL}, } var batchResponse batchSetSoftwareInstallersResponse - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages := waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11199,7 +11199,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec // Switch self-service flag softwareToInstall[0].SelfService = true - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11220,7 +11220,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec withUpdatedPreinstallQuery := []fleet.SoftwareInstallerPayload{ {URL: srv.URL, PreInstallQuery: "SELECT * FROM os_version"}, } - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: withUpdatedPreinstallQuery}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: withUpdatedPreinstallQuery}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11265,7 +11265,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec withUpdatedInstallScript := []fleet.SoftwareInstallerPayload{ {URL: srv.URL, InstallScript: "apt install ruby"}, } - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: withUpdatedInstallScript}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: withUpdatedInstallScript}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11287,7 +11287,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec trailer = " " // add a character to the response for the installer HTTP call to ensure the file hashes differently // update package - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: withUpdatedInstallScript}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: withUpdatedInstallScript}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11330,7 +11330,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec require.Equal(t, fleet.SoftwareUninstallPending, *afterPreinstallHostResp.Software[0].Status) // delete all installers - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: []fleet.SoftwareInstallerPayload{}}, http.StatusOK, &batchResponse, "team_name", tm.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: []fleet.SoftwareInstallerPayload{}}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) require.Len(t, packages, 0) @@ -11396,7 +11396,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersWithPolic }, } var batchResponse batchSetSoftwareInstallersResponse - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", team1.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", team1.Name) packages := waitBatchSetSoftwareInstallersCompleted(t, s, team1.Name, batchResponse.RequestUUID) require.Len(t, packages, 1) require.NotNil(t, packages[0].TitleID) @@ -11413,7 +11413,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersWithPolic URL: srv.URL + "/ruby.deb", }, } - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", team2.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", team2.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, team2.Name, batchResponse.RequestUUID) sort.Slice(packages, func(i, j int) bool { return packages[i].URL < packages[j].URL @@ -11456,7 +11456,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersWithPolic // Get rid of all installers in team1. softwareToInstall = []fleet.SoftwareInstallerPayload{} - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusOK, &batchResponse, "team_name", team1.Name) + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", team1.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, team1.Name, batchResponse.RequestUUID) require.Len(t, packages, 0) diff --git a/server/service/software_installers.go b/server/service/software_installers.go index 0045ed962b49..ba6bf84bb213 100644 --- a/server/service/software_installers.go +++ b/server/service/software_installers.go @@ -560,6 +560,7 @@ type batchSetSoftwareInstallersResponse struct { } func (r batchSetSoftwareInstallersResponse) error() error { return r.Err } +func (r batchSetSoftwareInstallersResponse) Status() int { return http.StatusAccepted } func batchSetSoftwareInstallersEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*batchSetSoftwareInstallersRequest)