From 4e35de2ac3bbece977c36c9f94c47bdde7bf0526 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 13 Mar 2026 08:38:55 -0500 Subject: [PATCH] Update fleetctl client urls and params (#41463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Related issue:** Resolves #41385 # Details This PR updates `fleetctl` to use the new API urls and params when communicating with Fleet server. This avoids deprecation warnings showing up on the server that users won't be able to fix. Most of the changes are straightforward `team_id` -> `fleet_id`. A couple of code changes have been pointed out. The most interesting is in icon URLs, which can be persisted in the database (so we'll need to do a migration in Fleet 5 if we want to drop support for `team_id`. Similarly the FMA download urls are briefly persisted in the db for the purpose of sending MDM commands. If we drop team_id support in Fleet 5 there could be a brief window where there are unprocessed commands in the db still with `team_id` in them, so we'll probably want to migrate those as well. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] 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/guides/committing-changes.md#changes-files) for more information. n/a - all internal ## Testing - [X] Added/updated automated tests - [X] QA'd all new/changed functionality manually - [X] ran `fleetctl gitops` on main and saw a bunch of deprecation warnings, ran it on this branch and the warnings were gone 💨 - [X] same with `fleetctl generate-gitops` - [X] ran `fleetctl get` commands and verified that the new URLs and params were used - [X] ran `fleetctl apply` commands and verified that the new URLs and params were used --- cmd/fleetctl/fleetctl/generate_gitops.go | 4 +-- cmd/fleetctl/fleetctl/generate_gitops_test.go | 14 ++++---- cmd/fleetctl/fleetctl/get.go | 4 +-- ee/server/service/in_house_apps.go | 5 ++- ee/server/service/software_installers_test.go | 2 +- ee/server/service/software_title_icons.go | 2 +- server/datastore/mysql/activities.go | 6 ++-- server/fleet/software_installer_test.go | 2 +- server/fleet/software_title_icons.go | 4 +-- .../platform/endpointer/json_key_rewriter.go | 14 ++++++++ .../endpointer/json_key_rewriter_test.go | 32 ++++++++++++++++++ server/service/client.go | 8 ++--- server/service/client_android_certificates.go | 2 +- server/service/client_appconfig.go | 13 +++++++- server/service/client_labels.go | 4 +-- server/service/client_live_query.go | 2 +- server/service/client_live_query_test.go | 2 +- server/service/client_mdm.go | 4 +-- server/service/client_policies.go | 16 +++++++-- server/service/client_profiles.go | 10 +++--- server/service/client_queries.go | 26 ++++++++++----- server/service/client_scripts.go | 8 ++--- server/service/client_software.go | 10 +++--- server/service/client_teams.go | 33 ++++++++++++------- server/service/hosts.go | 4 +-- server/service/integration_enterprise_test.go | 4 +-- server/service/integration_mdm_test.go | 4 +-- .../service/integration_vpp_install_test.go | 6 ++-- server/service/software_title_icons.go | 7 +++- server/service/transport.go | 26 +++++++++++++-- server/worker/jira.go | 2 +- server/worker/jira_test.go | 4 +-- server/worker/zendesk.go | 2 +- server/worker/zendesk_test.go | 4 +-- 34 files changed, 200 insertions(+), 90 deletions(-) diff --git a/cmd/fleetctl/fleetctl/generate_gitops.go b/cmd/fleetctl/fleetctl/generate_gitops.go index 3a9ddfb7b6..496b08944a 100644 --- a/cmd/fleetctl/fleetctl/generate_gitops.go +++ b/cmd/fleetctl/fleetctl/generate_gitops.go @@ -1388,7 +1388,7 @@ func (cmd *GenerateGitopsCommand) generateScripts(teamId *uint, teamName string) // Get scripts. query := "" if teamId != nil { - query = fmt.Sprintf("team_id=%d", *teamId) + query = fmt.Sprintf("fleet_id=%d", *teamId) } scripts, err := cmd.Client.ListScripts(query) if err != nil { @@ -1568,7 +1568,7 @@ func (cmd *GenerateGitopsCommand) generateSoftware(filePath string, teamID uint, return nil, nil // software is premium-only } - query := fmt.Sprintf("available_for_install=1&team_id=%d", teamID) + query := fmt.Sprintf("available_for_install=1&fleet_id=%d", teamID) software, err := cmd.Client.ListSoftwareTitles(query) if err != nil { fmt.Fprintf(cmd.CLI.App.ErrWriter, "Error getting software: %s\n", err) diff --git a/cmd/fleetctl/fleetctl/generate_gitops_test.go b/cmd/fleetctl/fleetctl/generate_gitops_test.go index 7f2c876920..79d2f86e1d 100644 --- a/cmd/fleetctl/fleetctl/generate_gitops_test.go +++ b/cmd/fleetctl/fleetctl/generate_gitops_test.go @@ -98,23 +98,23 @@ func (c *MockClient) ListTeams(query string) ([]fleet.Team, error) { func (MockClient) ListScripts(query string) ([]*fleet.Script, error) { switch query { - case "team_id=1": + case "fleet_id=1": return []*fleet.Script{{ ID: 2, TeamID: ptr.Uint(1), Name: "Script B.ps1", ScriptContentID: 2, }}, nil - case "team_id=0": + case "fleet_id=0": return []*fleet.Script{{ ID: 3, TeamID: ptr.Uint(0), Name: "Script Z.ps1", ScriptContentID: 3, }}, nil - case "team_id=2", "team_id=3", "team_id=4", "team_id=5": + case "fleet_id=2", "fleet_id=3", "fleet_id=4", "fleet_id=5": return nil, nil - case "team_id=6": + case "fleet_id=6": return nil, nil default: return nil, fmt.Errorf("unexpected query: %s", query) @@ -249,7 +249,7 @@ func (MockClient) GetTeam(teamID uint) (*fleet.Team, error) { func (MockClient) ListSoftwareTitles(query string) ([]fleet.SoftwareTitleListResult, error) { switch query { - case "available_for_install=1&team_id=1": + case "available_for_install=1&fleet_id=1": return []fleet.SoftwareTitleListResult{ { ID: 1, @@ -312,7 +312,7 @@ func (MockClient) ListSoftwareTitles(query string) ([]fleet.SoftwareTitleListRes }, }, }, nil - case "available_for_install=1&team_id=0": + case "available_for_install=1&fleet_id=0": return []fleet.SoftwareTitleListResult{}, nil default: return nil, fmt.Errorf("unexpected query: %s", query) @@ -1592,7 +1592,7 @@ type MockClientWithScriptPackage struct { func (c *MockClientWithScriptPackage) ListSoftwareTitles(query string) ([]fleet.SoftwareTitleListResult, error) { switch query { - case "available_for_install=1&team_id=2": + case "available_for_install=1&fleet_id=2": return []fleet.SoftwareTitleListResult{ { ID: 3, diff --git a/cmd/fleetctl/fleetctl/get.go b/cmd/fleetctl/fleetctl/get.go index fc81bd8c6a..21384cb464 100644 --- a/cmd/fleetctl/fleetctl/get.go +++ b/cmd/fleetctl/fleetctl/get.go @@ -884,7 +884,7 @@ func getHostsCommand() *cli.Command { query := url.Values{} query.Set("additional_info_filters", "*") if teamID := c.Uint(fleetFlagName); teamID > 0 { - query.Set("team_id", strconv.FormatUint(uint64(teamID), 10)) + query.Set("fleet_id", strconv.FormatUint(uint64(teamID), 10)) } if c.Bool("mdm") || c.Bool("mdm-pending") { @@ -1306,7 +1306,7 @@ func getSoftwareCommand() *cli.Command { teamID := c.Uint(fleetFlagName) if teamID != 0 { - query.Set("team_id", strconv.FormatUint(uint64(teamID), 10)) + query.Set("fleet_id", strconv.FormatUint(uint64(teamID), 10)) } if c.Bool("versions") { diff --git a/ee/server/service/in_house_apps.go b/ee/server/service/in_house_apps.go index 610b8ff021..8b67a55aa8 100644 --- a/ee/server/service/in_house_apps.go +++ b/ee/server/service/in_house_apps.go @@ -171,7 +171,7 @@ func (svc *Service) GetInHouseAppManifest(ctx context.Context, titleID uint, tea return nil, ctxerr.Wrap(ctx, err, "get in house app manifest: get in house app metadata") } - downloadURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app?team_id=%d", appConfig.ServerSettings.ServerURL, titleID, ptr.ValOrZero(teamID)) + downloadURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app?fleet_id=%d", appConfig.ServerSettings.ServerURL, titleID, ptr.ValOrZero(teamID)) if svc.config.S3.SoftwareInstallersCloudFrontSigner != nil { signedURL, err := svc.softwareInstallStore.Sign(ctx, meta.StorageID, fleet.InHouseAppSignedURLExpiry) @@ -184,7 +184,7 @@ func (svc *Service) GetInHouseAppManifest(ctx context.Context, titleID uint, tea } // Escape & characters in case of using CloudFront signed URL - var funcMap = map[string]any{ + funcMap := map[string]any{ "xml": mobileconfig.XMLEscapeString, } @@ -235,7 +235,6 @@ func (svc *Service) GetInHouseAppManifest(ctx context.Context, titleID uint, tea Name string URL string }{meta.BundleIdentifier, meta.Version, meta.SoftwareTitle, downloadURL}) - if err != nil { return nil, ctxerr.Wrap(ctx, err, "rendering app manifest") } diff --git a/ee/server/service/software_installers_test.go b/ee/server/service/software_installers_test.go index 4d3230221e..f1e30dd10e 100644 --- a/ee/server/service/software_installers_test.go +++ b/ee/server/service/software_installers_test.go @@ -512,7 +512,7 @@ func TestGetInHouseAppManifest(t *testing.T) { kind software-package url - https://example.com/api/latest/fleet/software/titles/1/in_house_app?team_id=0 + https://example.com/api/latest/fleet/software/titles/1/in_house_app?fleet_id=0 kind diff --git a/ee/server/service/software_title_icons.go b/ee/server/service/software_title_icons.go index ab887499b3..e93653cb53 100644 --- a/ee/server/service/software_title_icons.go +++ b/ee/server/service/software_title_icons.go @@ -126,7 +126,7 @@ func (svc *Service) UploadSoftwareTitleIcon(ctx context.Context, payload *fleet. // if anything on the icon has changed, we need to generate a new activity if icon == nil || icon.StorageID != softwareTitleIcon.StorageID || icon.Filename != softwareTitleIcon.Filename { - iconUrl := fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon?team_id=%d", softwareTitleIcon.SoftwareTitleID, softwareTitleIcon.TeamID) + iconUrl := softwareTitleIcon.IconUrl() activityDetailsForSoftwareTitleIcon, err := svc.ds.ActivityDetailsForSoftwareTitleIcon(ctxdb.RequirePrimary(ctx, true), payload.TeamID, payload.TitleID) if err != nil { return fleet.SoftwareTitleIcon{}, ctxerr.Wrap(ctx, err, "fetching software title icon activity details") diff --git a/server/datastore/mysql/activities.go b/server/datastore/mysql/activities.go index c14508c67b..466dcf9520 100644 --- a/server/datastore/mysql/activities.go +++ b/server/datastore/mysql/activities.go @@ -15,9 +15,7 @@ import ( "github.com/jmoiron/sqlx" ) -var ( - deleteIDsBatchSize = 1000 -) +var deleteIDsBatchSize = 1000 // ListHostUpcomingActivities returns the list of activities pending execution // or processing for the specific host. It is the "unified queue" of work to be @@ -1487,7 +1485,7 @@ WHERE return ctxerr.Wrap(ctx, err, "get in-house app title id") } - manifestURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app/manifest?team_id=%d", appConfig.ServerSettings.ServerURL, titleID, tid) + manifestURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app/manifest?fleet_id=%d", appConfig.ServerSettings.ServerURL, titleID, tid) // insert the nano command namedArgs := map[string]any{ diff --git a/server/fleet/software_installer_test.go b/server/fleet/software_installer_test.go index 4f30010fa2..1a4a533bfb 100644 --- a/server/fleet/software_installer_test.go +++ b/server/fleet/software_installer_test.go @@ -109,7 +109,7 @@ func TestForMyDevicePage(t *testing.T) { { name: "matching custom icon url", before: func() { - iconUrl = ptr.String("/api/latest/fleet/software/titles/42/icon?team_id=7") + iconUrl = ptr.String("/api/latest/fleet/software/titles/42/icon?fleet_id=7") hostSoftwareInstaller = HostSoftwareWithInstaller{ IconUrl: iconUrl, ID: 1, diff --git a/server/fleet/software_title_icons.go b/server/fleet/software_title_icons.go index b502812dee..dae94b8433 100644 --- a/server/fleet/software_title_icons.go +++ b/server/fleet/software_title_icons.go @@ -8,7 +8,7 @@ import ( "time" ) -var SoftwareTitleIconURLRegex = regexp.MustCompile(`fleet/software/titles/\d+/icon\?team_id=\d+`) +var SoftwareTitleIconURLRegex = regexp.MustCompile(`fleet/software/titles/\d+/icon\?(?:team_id|fleet_id)=\d+`) const SoftwareTitleIconSignedURLExpiry = 6 * time.Hour @@ -37,7 +37,7 @@ func (s *SoftwareTitleIcon) AuthzType() string { } func (s *SoftwareTitleIcon) IconUrl() string { - return fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon?team_id=%d", s.SoftwareTitleID, s.TeamID) + return fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon?fleet_id=%d", s.SoftwareTitleID, s.TeamID) } func (s *SoftwareTitleIcon) IconUrlWithDeviceToken(deviceToken string) string { diff --git a/server/platform/endpointer/json_key_rewriter.go b/server/platform/endpointer/json_key_rewriter.go index c03a1b4ebe..4eae18c8d5 100644 --- a/server/platform/endpointer/json_key_rewriter.go +++ b/server/platform/endpointer/json_key_rewriter.go @@ -145,6 +145,20 @@ func RewriteDeprecatedKeys(data []byte, rules []AliasRule) ([]byte, map[string]s return buf.Bytes(), deprecatedKeysMap, nil } +// RewriteOldToNewKeys is the reverse of RewriteDeprecatedKey; it takes +// the rules and reverses them before translating keys. +// Use this in situations where a payload was rewritten from new to old keys +// for deserialization, but you want to return a response with the new keys +// for forward compatibility. +func RewriteOldToNewKeys(data []byte, rules []AliasRule) ([]byte, error) { + reversed := make([]AliasRule, len(rules)) + for i, r := range rules { + reversed[i] = AliasRule{OldKey: r.NewKey, NewKey: r.OldKey} + } + result, _, err := RewriteDeprecatedKeys(data, reversed) + return result, err +} + // rewrite reads tokens from src, rewrites deprecated keys, checks for alias // conflicts, and writes the transformed JSON to w. func (r *JSONKeyRewriteReader) rewrite(src io.Reader, w io.Writer) error { diff --git a/server/platform/endpointer/json_key_rewriter_test.go b/server/platform/endpointer/json_key_rewriter_test.go index b3ea1c606f..9216aa6938 100644 --- a/server/platform/endpointer/json_key_rewriter_test.go +++ b/server/platform/endpointer/json_key_rewriter_test.go @@ -468,3 +468,35 @@ func TestAliasConflictError_ErrorMessage(t *testing.T) { assert.Contains(t, err.Error(), "team_id") assert.Contains(t, err.Error(), "fleet_id") } + +func TestRewriteOldToNewKeys(t *testing.T) { + rules := []AliasRule{ + {OldKey: "team_id", NewKey: "fleet_id"}, + {OldKey: "team", NewKey: "fleet"}, + {OldKey: "custom_settings", NewKey: "configuration_profiles"}, + } + + t.Run("rewrites old keys to new", func(t *testing.T) { + input := `{"team_id":42,"name":"hello","team":"engineering"}` + out, err := RewriteOldToNewKeys([]byte(input), rules) + require.NoError(t, err) + + var result map[string]any + require.NoError(t, json.Unmarshal(out, &result)) + assert.Equal(t, float64(42), result["fleet_id"]) + assert.Equal(t, "engineering", result["fleet"]) + assert.Equal(t, "hello", result["name"]) + assert.Nil(t, result["team_id"]) + assert.Nil(t, result["team"]) + }) + + t.Run("new keys pass through unchanged", func(t *testing.T) { + input := `{"fleet_id":42}` + out, err := RewriteOldToNewKeys([]byte(input), rules) + require.NoError(t, err) + + var result map[string]any + require.NoError(t, json.Unmarshal(out, &result)) + assert.Equal(t, float64(42), result["fleet_id"]) + }) +} diff --git a/server/service/client.go b/server/service/client.go index a308f599c0..19c8094126 100644 --- a/server/service/client.go +++ b/server/service/client.go @@ -2780,12 +2780,12 @@ func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers [] for _, softwareInstaller := range teamSoftwareInstallers { if softwareInstaller.TitleID == nil { // Should not happen, but to not panic we just log a warning. - logFn("[!] software installer without title id: team_id=%d, url=%s\n", *teamID, softwareInstaller.URL) + logFn("[!] software installer without title id: fleet_id=%d, url=%s\n", *teamID, softwareInstaller.URL) continue } if softwareInstaller.URL == "" && softwareInstaller.HashSHA256 == "" { // Should not happen because we previously applied packages via gitops, but to not panic we just log a warning. - logFn("[!] software installer without url: team_id=%d, title_id=%d\n", *teamID, *softwareInstaller.TitleID) + logFn("[!] software installer without url: fleet_id=%d, title_id=%d\n", *teamID, *softwareInstaller.TitleID) continue } softwareTitleIDsByInstallerURL[softwareInstaller.URL] = *softwareInstaller.TitleID @@ -2797,12 +2797,12 @@ func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers [] } if vppApp.TitleID == nil { // Should not happen, but to not panic we just log a warning. - logFn("[!] VPP app without title id: team_id=%d, app_store_id=%s\n", *teamID, vppApp.AppStoreID) + logFn("[!] VPP app without title id: fleet_id=%d, app_store_id=%s\n", *teamID, vppApp.AppStoreID) continue } if vppApp.AppStoreID == "" { // Should not happen because we previously applied apps via gitops, but to not panic we just log a warning. - logFn("[!] VPP app without app ID: team_id=%d, title_id=%d\n", *teamID, *vppApp.TitleID) + logFn("[!] VPP app without app ID: fleet_id=%d, title_id=%d\n", *teamID, *vppApp.TitleID) continue } softwareTitleIDsByAppStoreAppID[vppApp.AppStoreID] = *vppApp.TitleID diff --git a/server/service/client_android_certificates.go b/server/service/client_android_certificates.go index 624ce8ca70..117b0647d4 100644 --- a/server/service/client_android_certificates.go +++ b/server/service/client_android_certificates.go @@ -8,7 +8,7 @@ import ( func (c *Client) GetCertificateTemplates(teamID string) ([]*fleet.CertificateTemplateResponseSummary, error) { verb, path := "GET", "/api/latest/fleet/certificates" var responseBody listCertificateTemplatesResponse - query := "team_id=" + teamID + query := "fleet_id=" + teamID err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query) if err != nil { return nil, err diff --git a/server/service/client_appconfig.go b/server/service/client_appconfig.go index 6c561da044..ab5627619b 100644 --- a/server/service/client_appconfig.go +++ b/server/service/client_appconfig.go @@ -1,7 +1,10 @@ package service import ( + "encoding/json" + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/platform/endpointer" "github.com/fleetdm/fleet/v4/server/version" ) @@ -9,7 +12,15 @@ import ( func (c *Client) ApplyAppConfig(payload interface{}, opts fleet.ApplySpecOptions) error { verb, path := "PATCH", "/api/latest/fleet/config" var responseBody appConfigResponse - return c.authenticatedRequestWithQuery(payload, verb, path, &responseBody, opts.RawQuery()) + data, err := json.Marshal(payload) + if err != nil { + return err + } + data, err = endpointer.RewriteOldToNewKeys(data, endpointer.ExtractAliasRules(fleet.AppConfig{})) + if err != nil { + return err + } + return c.authenticatedRequestWithQuery(data, verb, path, &responseBody, opts.RawQuery()) } // ApplyNoTeamProfiles sends the list of profiles to be applied for the hosts diff --git a/server/service/client_labels.go b/server/service/client_labels.go index e937047762..a1343e1a79 100644 --- a/server/service/client_labels.go +++ b/server/service/client_labels.go @@ -24,7 +24,7 @@ func (c *Client) ApplyLabels( verb, path, &responseBody, - fmt.Sprintf("team_id=%d", *teamID), + fmt.Sprintf("fleet_id=%d", *teamID), ) } return c.authenticatedRequest(req, verb, path, &responseBody) @@ -42,7 +42,7 @@ func (c *Client) GetLabel(name string) (*fleet.LabelSpec, error) { func (c *Client) GetLabels(teamID uint) ([]*fleet.LabelSpec, error) { verb, path := "GET", "/api/latest/fleet/spec/labels" var responseBody getLabelSpecsResponse - err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, fmt.Sprintf("team_id=%d", teamID)) + err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, fmt.Sprintf("fleet_id=%d", teamID)) return responseBody.Specs, err } diff --git a/server/service/client_live_query.go b/server/service/client_live_query.go index 6b7fe54605..9fa297bfb2 100644 --- a/server/service/client_live_query.go +++ b/server/service/client_live_query.go @@ -74,7 +74,7 @@ func (c *Client) LiveQueryWithContext( QuerySQL: query, Selected: distributedQueryCampaignTargetsByIdentifiers{Labels: labels, Hosts: hostIdentifiers}, } - verb, path := "POST", "/api/latest/fleet/queries/run_by_identifiers" + verb, path := "POST", "/api/latest/fleet/reports/run_by_identifiers" var responseBody createDistributedQueryCampaignResponse err := c.authenticatedRequest(req, verb, path, &responseBody) if err != nil { diff --git a/server/service/client_live_query_test.go b/server/service/client_live_query_test.go index f797c025b6..bf6726bce2 100644 --- a/server/service/client_live_query_test.go +++ b/server/service/client_live_query_test.go @@ -20,7 +20,7 @@ func TestLiveQueryWithContext(t *testing.T) { upgrader := websocket.Upgrader{} ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { - case "/api/latest/fleet/queries/run_by_identifiers": + case "/api/latest/fleet/reports/run_by_identifiers": resp := createDistributedQueryCampaignResponse{ Campaign: &fleet.DistributedQueryCampaign{ UpdateCreateTimestamps: fleet.UpdateCreateTimestamps{ diff --git a/server/service/client_mdm.go b/server/service/client_mdm.go index a722325f06..3a5b9d85ec 100644 --- a/server/service/client_mdm.go +++ b/server/service/client_mdm.go @@ -116,8 +116,8 @@ func (c *Client) UploadBootstrapPackage(pkg *fleet.MDMAppleBootstrapPackage, dry return err } - // add the team_id field - if err := w.WriteField("team_id", fmt.Sprint(pkg.TeamID)); err != nil { + // add the fleet_id field + if err := w.WriteField("fleet_id", fmt.Sprint(pkg.TeamID)); err != nil { return err } diff --git a/server/service/client_policies.go b/server/service/client_policies.go index 388d7d80d9..858ca00c73 100644 --- a/server/service/client_policies.go +++ b/server/service/client_policies.go @@ -1,9 +1,11 @@ package service import ( + "encoding/json" "fmt" "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/platform/endpointer" ) func (c *Client) CreateGlobalPolicy(name, query, description, resolution, platform string) error { @@ -25,14 +27,22 @@ func (c *Client) ApplyPolicies(specs []*fleet.PolicySpec) error { req := applyPolicySpecsRequest{Specs: specs} verb, path := "POST", "/api/latest/fleet/spec/policies" var responseBody applyPolicySpecsResponse - return c.authenticatedRequest(req, verb, path, &responseBody) + data, err := json.Marshal(req) + if err != nil { + return err + } + data, err = endpointer.RewriteOldToNewKeys(data, endpointer.ExtractAliasRules(req)) + if err != nil { + return err + } + return c.authenticatedRequest(data, verb, path, &responseBody) } // GetPolicies retrieves the list of Policies. Inherited policies are excluded. func (c *Client) GetPolicies(teamID *uint) ([]*fleet.Policy, error) { verb, path := "GET", "" if teamID != nil { - path = fmt.Sprintf("/api/latest/fleet/teams/%d/policies", *teamID) + path = fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", *teamID) } else { path = "/api/latest/fleet/policies" } @@ -50,7 +60,7 @@ func (c *Client) DeletePolicies(teamID *uint, ids []uint) error { verb, path := "POST", "" req := deleteTeamPoliciesRequest{IDs: ids} if teamID != nil { - path = fmt.Sprintf("/api/latest/fleet/teams/%d/policies/delete", *teamID) + path = fmt.Sprintf("/api/latest/fleet/fleets/%d/policies/delete", *teamID) req.TeamID = *teamID } else { path = "/api/latest/fleet/policies/delete" diff --git a/server/service/client_profiles.go b/server/service/client_profiles.go index ca80df870d..5962fba628 100644 --- a/server/service/client_profiles.go +++ b/server/service/client_profiles.go @@ -27,7 +27,7 @@ func (c *Client) ListProfiles(teamID *uint) ([]*fleet.MDMAppleConfigProfile, err verb, path := "GET", "/api/latest/fleet/mdm/apple/profiles" query := make(url.Values) if teamID != nil { - query.Add("team_id", strconv.FormatUint(uint64(*teamID), 10)) + query.Add("fleet_id", strconv.FormatUint(uint64(*teamID), 10)) } var responseBody listMDMAppleConfigProfilesResponse if err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query.Encode()); err != nil { @@ -40,7 +40,7 @@ func (c *Client) ListConfigurationProfiles(teamID *uint) ([]*fleet.MDMConfigProf verb, path := "GET", "/api/latest/fleet/configuration_profiles" query := make(url.Values) if teamID != nil { - query.Add("team_id", strconv.FormatUint(uint64(*teamID), 10)) + query.Add("fleet_id", strconv.FormatUint(uint64(*teamID), 10)) } var responseBody listMDMConfigProfilesResponse if err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query.Encode()); err != nil { @@ -77,7 +77,7 @@ func (c *Client) AddProfile(teamID uint, configurationProfile []byte) (uint, err } body := &bytes.Buffer{} writer := multipart.NewWriter(body) - teamIDField, err := writer.CreateFormField("team_id") + teamIDField, err := writer.CreateFormField("fleet_id") if err != nil { return 0, err } @@ -138,7 +138,7 @@ func (c *Client) GetConfigProfilesSummary(teamID *uint) (*fleet.MDMProfilesSumma verb, path := "GET", "/api/latest/fleet/mdm/profiles/summary" query := make(url.Values) if teamID != nil { - query.Add("team_id", strconv.FormatUint(uint64(*teamID), 10)) + query.Add("fleet_id", strconv.FormatUint(uint64(*teamID), 10)) } var responseBody getMDMProfilesSummaryResponse if err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query.Encode()); err != nil { @@ -152,7 +152,7 @@ func (c *Client) GetAppleMDMEnrollmentProfile(teamID uint) (*fleet.MDMAppleSetup verb, path := "GET", "/api/latest/fleet/enrollment_profiles/automatic" var query string if teamID != 0 { - query = fmt.Sprintf("team_id=%d", teamID) + query = fmt.Sprintf("fleet_id=%d", teamID) } var responseBody createMDMAppleSetupAssistantResponse if err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query); err != nil { diff --git a/server/service/client_queries.go b/server/service/client_queries.go index 0870482632..502bc33560 100644 --- a/server/service/client_queries.go +++ b/server/service/client_queries.go @@ -1,27 +1,37 @@ package service import ( + "encoding/json" "fmt" "net/url" "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/platform/endpointer" ) // ApplyQueries sends the list of Queries to be applied (upserted) to the // Fleet instance. func (c *Client) ApplyQueries(specs []*fleet.QuerySpec) error { req := applyQuerySpecsRequest{Specs: specs} - verb, path := "POST", "/api/latest/fleet/spec/queries" + verb, path := "POST", "/api/latest/fleet/spec/reports" var responseBody applyQuerySpecsResponse - return c.authenticatedRequest(req, verb, path, &responseBody) + data, err := json.Marshal(req) + if err != nil { + return err + } + data, err = endpointer.RewriteOldToNewKeys(data, endpointer.ExtractAliasRules(req)) + if err != nil { + return err + } + return c.authenticatedRequest(data, verb, path, &responseBody) } // GetQuerySpec returns the query spec of a query by its team+name. func (c *Client) GetQuerySpec(teamID *uint, name string) (*fleet.QuerySpec, error) { - verb, path := "GET", "/api/latest/fleet/spec/queries/"+url.PathEscape(name) + verb, path := "GET", "/api/latest/fleet/spec/reports/"+url.PathEscape(name) query := url.Values{} if teamID != nil { - query.Set("team_id", fmt.Sprint(*teamID)) + query.Set("fleet_id", fmt.Sprint(*teamID)) } var responseBody getQuerySpecResponse err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query.Encode()) @@ -30,10 +40,10 @@ func (c *Client) GetQuerySpec(teamID *uint, name string) (*fleet.QuerySpec, erro // GetQueries retrieves the list of all Queries. func (c *Client) GetQueries(teamID *uint, name *string) ([]fleet.Query, error) { - verb, path := "GET", "/api/latest/fleet/queries" + verb, path := "GET", "/api/latest/fleet/reports" query := url.Values{} if teamID != nil { - query.Set("team_id", fmt.Sprint(*teamID)) + query.Set("fleet_id", fmt.Sprint(*teamID)) } if name != nil { query.Set("query", *name) @@ -48,7 +58,7 @@ func (c *Client) GetQueries(teamID *uint, name *string) ([]fleet.Query, error) { // DeleteQuery deletes the query with the matching name. func (c *Client) DeleteQuery(name string) error { - verb, path := "DELETE", "/api/latest/fleet/queries/"+url.PathEscape(name) + verb, path := "DELETE", "/api/latest/fleet/reports/"+url.PathEscape(name) var responseBody deleteQueryResponse return c.authenticatedRequest(nil, verb, path, &responseBody) } @@ -56,7 +66,7 @@ func (c *Client) DeleteQuery(name string) error { // DeleteQueries deletes several queries. func (c *Client) DeleteQueries(ids []uint) error { req := deleteQueriesRequest{IDs: ids} - verb, path := "POST", "/api/latest/fleet/queries/delete" + verb, path := "POST", "/api/latest/fleet/reports/delete" var responseBody deleteQueriesResponse return c.authenticatedRequest(req, verb, path, &responseBody) } diff --git a/server/service/client_scripts.go b/server/service/client_scripts.go index cafd07c68a..7b9a42cfbf 100644 --- a/server/service/client_scripts.go +++ b/server/service/client_scripts.go @@ -159,7 +159,7 @@ func (c *Client) validateMacOSSetupScript(fileName string) ([]byte, error) { func (c *Client) deleteMacOSSetupScript(teamID *uint) error { var query string if teamID != nil { - query = fmt.Sprintf("team_id=%d", *teamID) + query = fmt.Sprintf("fleet_id=%d", *teamID) } verb, path := "DELETE", "/api/latest/fleet/setup_experience/script" @@ -181,9 +181,9 @@ func (c *Client) uploadMacOSSetupScript(filename string, data []byte, teamID *ui return err } - // add the team_id field + // add the fleet_id field if teamID != nil { - if err := w.WriteField("team_id", fmt.Sprint(*teamID)); err != nil { + if err := w.WriteField("fleet_id", fmt.Sprint(*teamID)); err != nil { return err } } @@ -248,7 +248,7 @@ func (c *Client) GetSetupExperienceScript(teamID uint) (*fleet.Script, error) { verb, path := "GET", "/api/latest/fleet/setup_experience/script" var query string if teamID != 0 { - query = fmt.Sprintf("team_id=%d", teamID) + query = fmt.Sprintf("fleet_id=%d", teamID) } var responseBody getSetupExperienceScriptResponse err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query) diff --git a/server/service/client_software.go b/server/service/client_software.go index 16156f64ed..b0e5145827 100644 --- a/server/service/client_software.go +++ b/server/service/client_software.go @@ -40,7 +40,7 @@ func (c *Client) ListSoftwareTitles(query string) ([]fleet.SoftwareTitleListResu func (c *Client) GetSetupExperienceSoftware(platform string, teamID uint) ([]fleet.SoftwareTitleListResult, error) { verb, path := "GET", "/api/latest/fleet/setup_experience/software" var responseBody getSetupExperienceSoftwareResponse - query := fmt.Sprintf("platform=%s&team_id=%d", platform, teamID) + query := fmt.Sprintf("platform=%s&fleet_id=%d", platform, teamID) err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query) if err != nil { return nil, err @@ -54,7 +54,7 @@ func (c *Client) GetSetupExperienceSoftware(platform string, teamID uint) ([]fle func (c *Client) GetSoftwareTitleByID(ID uint, teamID *uint) (*fleet.SoftwareTitle, error) { var query string if teamID != nil { - query = fmt.Sprintf("team_id=%d", *teamID) + query = fmt.Sprintf("fleet_id=%d", *teamID) } verb, path := "GET", "/api/latest/fleet/software/titles/"+fmt.Sprint(ID) var responseBody getSoftwareTitleResponse @@ -67,7 +67,7 @@ func (c *Client) GetSoftwareTitleByID(ID uint, teamID *uint) (*fleet.SoftwareTit func (c *Client) GetSoftwareTitleIcon(titleID uint, teamID uint) ([]byte, error) { verb, path := "GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon", titleID) - response, err := c.AuthenticatedDo(verb, path, fmt.Sprintf("team_id=%d", teamID), nil) + response, err := c.AuthenticatedDo(verb, path, fmt.Sprintf("fleet_id=%d", teamID), nil) if err != nil { return nil, fmt.Errorf("%s %s: %w", verb, path, err) } @@ -211,7 +211,7 @@ func (c *Client) putIcon(teamID uint, titleID uint, writer *multipart.Writer, bu context.Background(), "PUT", fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon", titleID), - fmt.Sprintf("team_id=%d", teamID), + fmt.Sprintf("fleet_id=%d", teamID), buf.Bytes(), map[string]string{ "Content-Type": writer.FormDataContentType(), @@ -235,7 +235,7 @@ func (c *Client) DeleteIcon(teamID uint, titleID uint) error { response, err := c.AuthenticatedDo( "DELETE", fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon", titleID), - fmt.Sprintf("team_id=%d", teamID), + fmt.Sprintf("fleet_id=%d", teamID), nil, ) if err != nil { diff --git a/server/service/client_teams.go b/server/service/client_teams.go index ba46171414..dab2e36866 100644 --- a/server/service/client_teams.go +++ b/server/service/client_teams.go @@ -7,11 +7,12 @@ import ( "strconv" "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/platform/endpointer" ) // ListTeams retrieves the list of teams. func (c *Client) ListTeams(query string) ([]fleet.Team, error) { - verb, path := "GET", "/api/latest/fleet/teams" + verb, path := "GET", "/api/latest/fleet/fleets" var responseBody listTeamsResponse err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query) if err != nil { @@ -25,7 +26,7 @@ func (c *Client) CreateTeam(teamPayload fleet.TeamPayload) (*fleet.Team, error) req := createTeamRequest{ TeamPayload: teamPayload, } - verb, path := "POST", "/api/latest/fleet/teams" + verb, path := "POST", "/api/latest/fleet/fleets" var responseBody teamResponse err := c.authenticatedRequest(req, verb, path, &responseBody) if err != nil { @@ -35,7 +36,7 @@ func (c *Client) CreateTeam(teamPayload fleet.TeamPayload) (*fleet.Team, error) } func (c *Client) GetTeam(teamID uint) (*fleet.Team, error) { - verb, path := "GET", fmt.Sprintf("/api/latest/fleet/teams/%d", teamID) + verb, path := "GET", fmt.Sprintf("/api/latest/fleet/fleets/%d", teamID) var responseBody getTeamResponse if err := c.authenticatedRequest(nil, verb, path, &responseBody); err != nil { return nil, err @@ -45,7 +46,7 @@ func (c *Client) GetTeam(teamID uint) (*fleet.Team, error) { // DeleteTeam deletes a team. func (c *Client) DeleteTeam(teamID uint) error { - verb, path := "DELETE", "/api/latest/fleet/teams/"+strconv.FormatUint(uint64(teamID), 10) + verb, path := "DELETE", "/api/latest/fleet/fleets/"+strconv.FormatUint(uint64(teamID), 10) var responseBody deleteTeamResponse return c.authenticatedRequest(nil, verb, path, &responseBody) } @@ -53,9 +54,19 @@ func (c *Client) DeleteTeam(teamID uint) error { // ApplyTeams sends the list of Teams to be applied to the // Fleet instance. func (c *Client) ApplyTeams(specs []json.RawMessage, opts fleet.ApplyTeamSpecOptions) (map[string]uint, error) { - verb, path := "POST", "/api/latest/fleet/spec/teams" + verb, path := "POST", "/api/latest/fleet/spec/fleets" var responseBody applyTeamSpecsResponse - params := map[string]interface{}{"specs": specs} + // Rewrite deprecated key names in each team spec to use the new names. + rules := endpointer.ExtractAliasRules(fleet.TeamSpec{}) + rewritten := make([]json.RawMessage, len(specs)) + for i, spec := range specs { + updated, err := endpointer.RewriteOldToNewKeys(spec, rules) + if err != nil { + return nil, err + } + rewritten[i] = updated + } + params := map[string]any{"specs": rewritten} if opts.DryRun && opts.DryRunAssumptions != nil { params["dry_run_assumptions"] = opts.DryRunAssumptions } @@ -68,7 +79,7 @@ func (c *Client) ApplyTeams(specs []json.RawMessage, opts fleet.ApplyTeamSpecOpt // PatchFleet sends a partial update to the specified team. func (c *Client) PatchFleet(teamID uint, payload fleet.TeamPayload) error { - verb, path := "PATCH", "/api/latest/fleet/teams/"+strconv.FormatUint(uint64(teamID), 10) + verb, path := "PATCH", "/api/latest/fleet/fleets/"+strconv.FormatUint(uint64(teamID), 10) var resp teamResponse return c.authenticatedRequest(payload, verb, path, &resp) } @@ -81,7 +92,7 @@ func (c *Client) ApplyTeamProfiles(tmName string, profiles []fleet.MDMProfileBat if err != nil { return err } - query.Add("team_name", tmName) + query.Add("fleet_name", tmName) if opts.DryRunAssumptions != nil && opts.DryRunAssumptions.WindowsEnabledAndConfigured.Valid { query.Add("assume_enabled", strconv.FormatBool(opts.DryRunAssumptions.WindowsEnabledAndConfigured.Value)) } @@ -96,7 +107,7 @@ func (c *Client) ApplyTeamScripts(tmName string, scripts []fleet.ScriptPayload, if err != nil { return nil, err } - query.Add("team_name", tmName) + query.Add("fleet_name", tmName) var resp batchSetScriptsResponse err = c.authenticatedRequestWithQuery(map[string]interface{}{"scripts": scripts}, verb, path, &resp, query.Encode()) @@ -108,7 +119,7 @@ func (c *Client) ApplyTeamSoftwareInstallers(tmName string, softwareInstallers [ if err != nil { return nil, err } - query.Add("team_name", tmName) + query.Add("fleet_name", tmName) return c.applySoftwareInstallers(softwareInstallers, query, opts.DryRun) } @@ -117,7 +128,7 @@ func (c *Client) ApplyTeamAppStoreAppsAssociation(tmName string, vppBatchPayload if err != nil { return nil, err } - query.Add("team_name", tmName) + query.Add("fleet_name", tmName) return c.applyAppStoreAppsAssociation(vppBatchPayload, query) } diff --git a/server/service/hosts.go b/server/service/hosts.go index 12236d2299..b099218368 100644 --- a/server/service/hosts.go +++ b/server/service/hosts.go @@ -3232,12 +3232,12 @@ func hostListOptionsFromFilters(filter *map[string]interface{}) (*fleet.HostList } else { return nil, nil, badRequest("label_id must be a number") } - case "team_id": + case "fleet_id", "team_id": if teamID, ok := v.(float64); ok { // json unmarshals numbers as float64 teamID := uint(teamID) opt.TeamFilter = &teamID } else { - return nil, nil, badRequest("team_id must be a number") + return nil, nil, badRequest("fleet_id must be a number") } case "status": status, ok := v.(string) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 6de834a163..ef12117d77 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -12517,7 +12517,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD // check activity s.lastActivityOfTypeMatches(fleet.ActivityTypeDeletedSoftware{}.ActivityName(), - fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "software_icon_url": "/api/latest/fleet/software/titles/%d/icon?team_id=0", "team_name": null, "team_id": null, "fleet_name": null, "fleet_id": null, "self_service": true}`, titleID), 0) + fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "software_icon_url": "/api/latest/fleet/software/titles/%d/icon?fleet_id=0", "team_name": null, "team_id": null, "fleet_name": null, "fleet_id": null, "self_service": true}`, titleID), 0) // download the installer, not found anymore s.Do("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package?alt=media", titleID), nil, http.StatusNotFound, "team_id", fmt.Sprintf("%d", 0)) @@ -13043,7 +13043,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareTitleIcons() { headers, ) result := parsePutResponse(resp) - iconUrl := fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon?team_id=%d", titleID, tm.ID) + iconUrl := fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon?fleet_id=%d", titleID, tm.ID) require.Nil(t, result.Err) require.Contains(t, result.IconUrl, iconUrl) diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index f3a6ebe660..96124fa2d5 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -13841,7 +13841,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() { "team_id", fmt.Sprint(team.ID)) s.lastActivityMatches(fleet.ActivityDeletedAppStoreApp{}.ActivityName(), - fmt.Sprintf(`{"team_name": "%s", "fleet_name": "%s", "software_title": "%s", "app_store_id": "%s", "software_icon_url": "/api/latest/fleet/software/titles/%d/icon?team_id=%d", "team_id": %d, "fleet_id": %d, "platform": "%s"}`, team.Name, team.Name, + fmt.Sprintf(`{"team_name": "%s", "fleet_name": "%s", "software_title": "%s", "app_store_id": "%s", "software_icon_url": "/api/latest/fleet/software/titles/%d/icon?fleet_id=%d", "team_id": %d, "fleet_id": %d, "platform": "%s"}`, team.Name, team.Name, addedApp.Name, addedApp.AdamID, macOSTitleID, team.ID, team.ID, team.ID, addedApp.Platform), 0) var count int @@ -14583,7 +14583,7 @@ func (s *integrationMDMTestSuite) TestNoTeamVPPAppIcons() { "team_id", fmt.Sprint(fleet.PolicyNoTeamID)) s.lastActivityMatches(fleet.ActivityDeletedAppStoreApp{}.ActivityName(), - fmt.Sprintf(`{"team_name": null, "fleet_name": null, "software_title": "%s", "app_store_id": "%s", "software_icon_url": "/api/latest/fleet/software/titles/%d/icon?team_id=%d", "team_id": %d, "fleet_id": %d, "platform": "%s"}`, + fmt.Sprintf(`{"team_name": null, "fleet_name": null, "software_title": "%s", "app_store_id": "%s", "software_icon_url": "/api/latest/fleet/software/titles/%d/icon?fleet_id=%d", "team_id": %d, "fleet_id": %d, "platform": "%s"}`, addedApp.Name, addedApp.AdamID, macOSTitleID, fleet.PolicyNoTeamID, fleet.PolicyNoTeamID, fleet.PolicyNoTeamID, addedApp.Platform), 0) mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { diff --git a/server/service/integration_vpp_install_test.go b/server/service/integration_vpp_install_test.go index 582d621b2a..510773d017 100644 --- a/server/service/integration_vpp_install_test.go +++ b/server/service/integration_vpp_install_test.go @@ -1612,7 +1612,7 @@ func (s *integrationMDMTestSuite) TestInHouseAppInstall() { assert.Equal(t, installCmdUUID, cmd.CommandUUID) // Points at the expected manifest URL - expectedManifestURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app/manifest?team_id=%d", s.server.URL, titleID, 0) + expectedManifestURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app/manifest?fleet_id=%d", s.server.URL, titleID, 0) assert.Contains(t, string(cmd.Raw), expectedManifestURL) cmd, err = iosDevice.Acknowledge(cmd.CommandUUID) @@ -1792,7 +1792,7 @@ func (s *integrationMDMTestSuite) TestInHouseAppSelfInstall() { assert.Equal(t, installCmdUUID, cmd.CommandUUID) // Points at the expected manifest URL - expectedManifestURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app/manifest?team_id=%d", s.server.URL, titleID, 0) + expectedManifestURL := fmt.Sprintf("%s/api/latest/fleet/software/titles/%d/in_house_app/manifest?fleet_id=%d", s.server.URL, titleID, 0) assert.Contains(t, string(cmd.Raw), expectedManifestURL) cmd, err = iosDevice.Acknowledge(cmd.CommandUUID) @@ -1891,7 +1891,7 @@ func (s *integrationMDMTestSuite) TestGetInHouseAppManifestUnsignedURL() { manifest := readManifest(res) require.NotNil(t, manifest) - require.Contains(t, string(manifest), fmt.Sprintf("/%d/in_house_app?team_id=%d", titleID, *teamID)) + require.Contains(t, string(manifest), fmt.Sprintf("/%d/in_house_app?fleet_id=%d", titleID, *teamID)) } func (s *integrationMDMTestSuite) addHostIdentityCertificate(hostUUID string, certSerial uint64) { diff --git a/server/service/software_title_icons.go b/server/service/software_title_icons.go index 70a6364669..0c0e5f3734 100644 --- a/server/service/software_title_icons.go +++ b/server/service/software_title_icons.go @@ -126,7 +126,12 @@ func (putSoftwareTitleIconRequest) DecodeRequest(ctx context.Context, r *http.Re if titleIDUint64 > math.MaxUint { return nil, &fleet.BadRequestError{Message: "title_id value too large"} } - teamID := r.URL.Query().Get("team_id") + // Accept both fleet_id and team_id without deprecation warning, since + // persisted icon URLs may still contain team_id. + teamID := r.URL.Query().Get("fleet_id") + if teamID == "" { + teamID = r.URL.Query().Get("team_id") + } if teamID == "" { return nil, &fleet.BadRequestError{Message: "team_id is required"} } diff --git a/server/service/transport.go b/server/service/transport.go index 2fb23ef14f..bbdb67d413 100644 --- a/server/service/transport.go +++ b/server/service/transport.go @@ -4,13 +4,16 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/http" "strconv" "strings" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" + "github.com/fleetdm/fleet/v4/server/contexts/logging" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/platform/endpointer" + platform_logging "github.com/fleetdm/fleet/v4/server/platform/logging" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/gorilla/mux" ) @@ -143,7 +146,14 @@ func hostListOptionsFromRequest(r *http.Request) (fleet.HostListOptions, error) teamID := r.URL.Query().Get("fleet_id") if teamID == "" { teamID = r.URL.Query().Get("team_id") - // TODO: warn about deprecated team_id parameter if team_id is used instead of fleet_id. + if teamID != "" && + platform_logging.TopicEnabled(platform_logging.DeprecatedFieldTopic) { + logging.WithLevel(r.Context(), slog.LevelWarn) + logging.WithExtras(r.Context(), + "deprecated_param", "team_id", + "deprecation_warning", "'team_id' is deprecated, use 'fleet_id' instead", + ) + } } if teamID != "" { id, err := strconv.ParseUint(teamID, 10, 32) @@ -618,8 +628,18 @@ func userListOptionsFromRequest(r *http.Request) (fleet.UserListOptions, error) } userOpts := fleet.UserListOptions{ListOptions: opt} - - if tid := r.URL.Query().Get("team_id"); tid != "" { + tid := r.URL.Query().Get("fleet_id") + if tid == "" { + tid = r.URL.Query().Get("team_id") + if tid != "" && platform_logging.TopicEnabled(platform_logging.DeprecatedFieldTopic) { + logging.WithLevel(r.Context(), slog.LevelWarn) + logging.WithExtras(r.Context(), + "deprecated_param", "team_id", + "deprecation_warning", "'team_id' is deprecated, use 'fleet_id' instead", + ) + } + } + if tid != "" { teamID, err := strconv.ParseUint(tid, 10, 64) if err != nil { return userOpts, ctxerr.Wrap(r.Context(), badRequest(fmt.Sprintf("Invalid team_id: %s", tid))) diff --git a/server/worker/jira.go b/server/worker/jira.go index 5f6d411a16..ce01e9feaa 100644 --- a/server/worker/jira.go +++ b/server/worker/jira.go @@ -87,7 +87,7 @@ This issue was created automatically by your Fleet Jira integration. * [{{ .DisplayName }}|{{ $.FleetURL }}/hosts/{{ .ID }}] {{ end }} -View hosts that failed {{ .PolicyName }} on the [*Hosts*|{{ .FleetURL }}/hosts/manage/?order_key=hostname&order_direction=asc&{{ if .TeamID }}team_id={{ .TeamID }}&{{ end }}policy_id={{ .PolicyID }}&policy_response=failing] page in Fleet. +View hosts that failed {{ .PolicyName }} on the [*Hosts*|{{ .FleetURL }}/hosts/manage/?order_key=hostname&order_direction=asc&{{ if .TeamID }}fleet_id={{ .TeamID }}&{{ end }}policy_id={{ .PolicyID }}&policy_response=failing] page in Fleet. ---- diff --git a/server/worker/jira_test.go b/server/worker/jira_test.go index bfe8fa243a..1f3e874f9c 100644 --- a/server/worker/jira_test.go +++ b/server/worker/jira_test.go @@ -146,14 +146,14 @@ func TestJiraRun(t *testing.T) { `{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": []}}`, `"summary":"test-policy policy failed on 0 host(s)"`, []string{"\\u0026policy_id=1\\u0026policy_response=failing"}, - "\\u0026team_id=", + "\\u0026fleet_id=", }, { "failing team policy", fleet.TierPremium, `{"failing_policy":{"policy_id": 2, "policy_name": "test-policy-2", "team_id": 123, "hosts": [{"id": 1, "hostname": "test-1"}, {"id": 2, "hostname": "test-2"}]}}`, `"summary":"test-policy-2 policy failed on 2 host(s)"`, - []string{"\\u0026team_id=123\\u0026policy_id=2\\u0026policy_response=failing"}, + []string{"\\u0026fleet_id=123\\u0026policy_id=2\\u0026policy_response=failing"}, "", }, { diff --git a/server/worker/zendesk.go b/server/worker/zendesk.go index d56fc2751a..f5a7cec7c1 100644 --- a/server/worker/zendesk.go +++ b/server/worker/zendesk.go @@ -88,7 +88,7 @@ This ticket was created automatically by your Fleet Zendesk integration. * [{{ .DisplayName }}]({{ $.FleetURL }}/hosts/{{ .ID }}) {{ end }} -View hosts that failed {{ .PolicyName }} on the [**Hosts**]({{ .FleetURL }}/hosts/manage/?order_key=hostname&order_direction=asc&{{ if .TeamID }}team_id={{ .TeamID }}&{{ end }}policy_id={{ .PolicyID }}&policy_response=failing) page in Fleet. +View hosts that failed {{ .PolicyName }} on the [**Hosts**]({{ .FleetURL }}/hosts/manage/?order_key=hostname&order_direction=asc&{{ if .TeamID }}fleet_id={{ .TeamID }}&{{ end }}policy_id={{ .PolicyID }}&policy_response=failing) page in Fleet. ---- diff --git a/server/worker/zendesk_test.go b/server/worker/zendesk_test.go index 98a2967ea9..e83ac97e73 100644 --- a/server/worker/zendesk_test.go +++ b/server/worker/zendesk_test.go @@ -129,14 +129,14 @@ func TestZendeskRun(t *testing.T) { `{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": [{"id": 123, "hostname": "host-123"}]}}`, `"subject":"test-policy policy failed on 1 host(s)"`, []string{"\\u0026policy_id=1\\u0026policy_response=failing"}, - "\\u0026team_id=", + "\\u0026fleet_id=", }, { "failing team policy", fleet.TierPremium, `{"failing_policy":{"policy_id": 2, "policy_name": "test-policy-2", "team_id": 123, "hosts": [{"id": 1, "hostname": "host-1"}, {"id": 2, "hostname": "host-2"}]}}`, `"subject":"test-policy-2 policy failed on 2 host(s)"`, - []string{"\\u0026team_id=123\\u0026policy_id=2\\u0026policy_response=failing"}, + []string{"\\u0026fleet_id=123\\u0026policy_id=2\\u0026policy_response=failing"}, "", }, {