Add more deprecation logs and mute by default (#40305)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #40122

# Details

* Adds deprecation warnings to `fleetctl apply`
* Adds alias conflict errors (i.e. using both new and deprecated keys in
the same spec) to `fleetctl apply`
* Adds logic around all deprecated field warnings to check the topic
first
* Disables deprecation warnings by default for `fleet serve`, `fleetctl
gitops` and `fleetctl apply`
* Enables deprecation warnings for dogfood via env var

To turn on warnings:
* In `fleet serve`, use either
`--logging_enable_topics=deprecated-field-names` or the
`FLEET_LOGGING_ENABLE_TOPICS=deprecated-field-names` env var
* In `fleetctl gitops` / `fleetctl apply` use either
`--enable-log-topics=deprecated-field-names` or
`FLEET_ENABLE_LOG_TOPICS=deprecated-field-names`

# 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/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually

tested in `fleetctl apply`, `fleet serve` and `fleet gitops` that
warnings are suppressed by default and added when the appropriate env
var or CLI option is used
This commit is contained in:
Scott Gress 2026-02-23 23:09:08 -06:00 committed by GitHub
parent e08318c9ab
commit 772fb12cf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 230 additions and 38 deletions

View file

@ -240,6 +240,7 @@ the way that the Fleet server works.
//
// For example:
// platform_logging.DisableTopic("deprecated-api-keys")
platform_logging.DisableTopic(platform_logging.DeprecatedFieldTopic)
// Apply log topic overrides from config. Enables run first, then
// disables, so disable wins on conflict.

View file

@ -9,6 +9,7 @@ import (
"github.com/fleetdm/fleet/v4/pkg/spec"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/urfave/cli/v2"
)
@ -49,8 +50,17 @@ func applyCommand() *cli.Command {
configFlag(),
contextFlag(),
debugFlag(),
enableLogTopicsFlag(),
disableLogTopicsFlag(),
},
Action: func(c *cli.Context) error {
// Disable field deprecation warnings for now.
// TODO - remove this in future release to unleash warnings.
logging.DisableTopic(logging.DeprecatedFieldTopic)
// Apply log topic overrides from flags/env vars.
applyLogTopicFlags(c)
if flFilename == "" {
return errors.New("-f must be specified")
}
@ -72,14 +82,17 @@ func applyCommand() *cli.Command {
return fmt.Errorf("Invalid file extension %s: only .yml or .yaml files can be applied", ext)
}
specs, err := spec.GroupFromBytes(b)
if err != nil {
return err
}
logf := func(format string, a ...interface{}) {
fmt.Fprintf(c.App.Writer, format, a...)
}
specs, err := spec.GroupFromBytes(b, spec.GroupFromBytesOpts{
LogFn: logf,
})
if err != nil {
return err
}
opts := fleet.ApplyClientSpecOptions{
ApplySpecOptions: fleet.ApplySpecOptions{
Force: flForce,

View file

@ -30,6 +30,7 @@ import (
"github.com/fleetdm/fleet/v4/server/mock"
mdmmock "github.com/fleetdm/fleet/v4/server/mock/mdm"
nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/google/uuid"
@ -122,6 +123,7 @@ spec:
}
func TestApplyUserRolesDeprecated(t *testing.T) {
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
_, ds := testing_utils.RunServerWithMockedDS(t)
ds.ListUsersFunc = func(ctx context.Context, opt fleet.UserListOptions) ([]*fleet.User, error) {
@ -175,9 +177,29 @@ spec:
team: team1
`)
require.NoError(t, err)
assert.Equal(t, "[+] applied user roles\n", RunAppForTest(t, []string{"apply", "-f", tmpFile.Name()}))
expected := "[!] In user_roles: `team` is deprecated, please use `fleet` instead.\n[!] In user_roles: `teams` is deprecated, please use `fleets` instead.\n[+] applied user roles\n"
assert.Equal(t, expected, RunAppForTest(t, []string{"apply", "-f", tmpFile.Name()}))
require.Len(t, userRoleSpecList[1].Teams, 1)
assert.Equal(t, fleet.RoleMaintainer, userRoleSpecList[1].Teams[0].Role)
_, err = tmpFile.WriteString(`
---
apiVersion: v1
kind: user_roles
spec:
roles:
admin1@example.com:
global_role: admin
teams: null
admin2@example.com:
global_role: null
teams:
- role: maintainer
team: team1
fleet: team1
`)
require.NoError(t, err)
RunAppCheckErr(t, []string{"apply", "-f", tmpFile.Name()}, "in user_roles spec: Conflicting field names: cannot specify both `team` (deprecated) and `fleet` in the same request")
}
func TestApplyTeamSpecs(t *testing.T) {
@ -663,6 +685,8 @@ func writeTmpJSON(t *testing.T, v any) string {
}
func TestApplyAppConfig(t *testing.T) {
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
_, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{License: license})
@ -788,6 +812,33 @@ spec:
// agent options were not modified, since they were not provided
assert.Equal(t, string(defaultAgentOpts), string(*savedAppConfig.AgentOptions))
// Test key rewriting (deprecated -> new)
name = writeTmpYml(t, `---
apiVersion: v1
kind: config
spec:
server_settings:
report_cap: 100
`)
assert.Equal(t, "[+] applied fleet config\n", RunAppForTest(t, []string{"apply", "-f", name}))
require.NotNil(t, savedAppConfig)
assert.Equal(t, 100, savedAppConfig.ServerSettings.QueryReportCap)
// Test deprecation warnings
expected := "[!] In config: `query_report_cap` is deprecated, please use `report_cap` instead.\n[+] applied fleet config\n"
name = writeTmpYml(t, `---
apiVersion: v1
kind: config
spec:
server_settings:
query_report_cap: 200
`)
assert.Equal(t, expected, RunAppForTest(t, []string{"apply", "-f", name}))
require.NotNil(t, savedAppConfig)
assert.Equal(t, 200, savedAppConfig.ServerSettings.QueryReportCap)
name = writeTmpYml(t, `---
apiVersion: v1
kind: config
@ -840,6 +891,20 @@ spec:
assert.Equal(t, newMDMSettings, savedAppConfig.MDM)
}
func TestApplyAppConfigAliasConfict(t *testing.T) {
// Test conflict error
name := writeTmpYml(t, `---
apiVersion: v1
kind: config
spec:
server_settings:
query_report_cap: 200
report_cap: 200
`)
RunAppCheckErr(t, []string{"apply", "-f", name}, "in config spec: Conflicting field names: cannot specify both `query_report_cap` (deprecated) and `report_cap` in the same request")
}
func TestApplyAppConfigDryRunIssue(t *testing.T) {
// reproduces the bug fixed by https://github.com/fleetdm/fleet/pull/8194
_, ds := testing_utils.RunServerWithMockedDS(t)
@ -1198,11 +1263,11 @@ kind: pack
spec:
name: osquery_monitoring
reports:
- query: osquery_version
- report: osquery_version
name: osquery_version_snapshot
interval: 7200
snapshot: true
- query: osquery_version
- report: osquery_version
name: osquery_version_differential
interval: 7200
`

View file

@ -9,6 +9,7 @@ import (
"github.com/fleetdm/fleet/v4/pkg/spec"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/urfave/cli/v2"
@ -80,6 +81,10 @@ func gitopsCommand() *cli.Command {
disableLogTopicsFlag(),
},
Action: func(c *cli.Context) error {
// Disable field deprecation warnings for now.
// TODO - remove this in future release to unleash warnings.
logging.DisableTopic(logging.DeprecatedFieldTopic)
logf := func(format string, a ...interface{}) {
_, _ = fmt.Fprintf(c.App.Writer, format, a...)
}

View file

@ -19,6 +19,7 @@ import (
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
"github.com/fleetdm/fleet/v4/server/dev_mode"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/test"
"github.com/go-git/go-git/v5"
@ -30,6 +31,7 @@ import (
func (s *enterpriseIntegrationGitopsTestSuite) TestDeleteMacOSSetupDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
user := s.createGitOpsUser(t)
fleetctlConfig := s.createFleetctlConfig(t, user)
@ -147,6 +149,8 @@ team_settings:
func (s *enterpriseIntegrationGitopsTestSuite) TestUnsetConfigurationProfileLabelsDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
user := s.createGitOpsUser(t)
@ -270,6 +274,8 @@ team_settings:
func (s *enterpriseIntegrationGitopsTestSuite) TestUnsetSoftwareInstallerLabelsDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
user := s.createGitOpsUser(t)
@ -416,6 +422,8 @@ team_settings:
func (s *enterpriseIntegrationGitopsTestSuite) TestNoTeamWebhookSettingsDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := t.Context()
user := s.createGitOpsUser(t)
@ -681,6 +689,8 @@ team_settings:
func (s *enterpriseIntegrationGitopsTestSuite) TestMacOSSetupDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
user := s.createGitOpsUser(t)
@ -820,6 +830,8 @@ team_settings:
func (s *enterpriseIntegrationGitopsTestSuite) TestIPASoftwareInstallersDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
user := s.createGitOpsUser(t)
@ -1033,6 +1045,8 @@ team_settings:
// are properly applied via GitOps.
func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsSoftwareDisplayNameDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
user := s.createGitOpsUser(t)
@ -1151,6 +1165,8 @@ team_settings:
// and fleet maintained apps are properly applied via GitOps.
func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsSoftwareIconsDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
user := s.createGitOpsUser(t)
@ -1336,6 +1352,8 @@ team_settings:
func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsTeamLabelsDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
user := s.createGitOpsUser(t)
@ -1527,6 +1545,8 @@ labels:
// Multiple repos are simulated by copying over the example repository multiple times.
func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsTeamLabelsMultipleReposDeprecated() {
t := s.T()
t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
ctx := context.Background()
type repoSetup struct {

View file

@ -191,7 +191,8 @@ func (s *enterpriseIntegrationGitopsTestSuite) assertDryRunOutput(t *testing.T,
s.assertDryRunOutputWithDeprecation(t, output, false)
}
func (s *enterpriseIntegrationGitopsTestSuite) assertDryRunOutputWithDeprecation(t *testing.T, output string, allowDeprecation bool) {
func (s *enterpriseIntegrationGitopsTestSuite) assertDryRunOutputWithDeprecation(t *testing.T, output string, expectDeprecation bool) {
var sawDeprecation bool
allowedVerbs := []string{
"moved",
"deleted",
@ -204,13 +205,17 @@ func (s *enterpriseIntegrationGitopsTestSuite) assertDryRunOutputWithDeprecation
pattern := fmt.Sprintf("\\[([+\\-!])] would've (%s)", strings.Join(allowedVerbs, "|"))
reg := regexp.MustCompile(pattern)
for line := range strings.SplitSeq(output, "\n") {
if allowDeprecation && line != "" && strings.Contains(line, "is deprecated") {
if expectDeprecation && line != "" && strings.Contains(line, "is deprecated") {
sawDeprecation = true
continue
}
if line != "" && !strings.Contains(line, "succeeded") {
assert.Regexp(t, reg, line, "on dry run")
}
}
if expectDeprecation {
assert.True(t, sawDeprecation, "expected to see deprecation warning in dry run output")
}
}
func (s *enterpriseIntegrationGitopsTestSuite) assertRealRunOutput(t *testing.T, output string) {
@ -244,6 +249,9 @@ func (s *enterpriseIntegrationGitopsTestSuite) assertRealRunOutputWithDeprecatio
// TestFleetGitops runs `fleetctl gitops` command on configs in https://github.com/fleetdm/fleet-gitops repo.
// Changes to that repo may cause this test to fail.
func (s *enterpriseIntegrationGitopsTestSuite) TestFleetGitops() {
os.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic)
defer os.Unsetenv("FLEET_ENABLE_LOG_TOPICS")
t := s.T()
user := s.createGitOpsUser(t)

View file

@ -68,6 +68,7 @@ locals {
OTEL_EXPORTER_OTLP_ENDPOINT = "https://otlp.signoz.dogfood.fleetdm.com"
# FLEET_LOGGING_TRACING_ENABLED = "true"
# FLEET_LOGGING_TRACING_TYPE = "elasticapm"
FLEET_LOGGING_ENABLE_TOPICS = "deprecated-field-names"
FLEET_MYSQL_MAX_OPEN_CONNS = "10"
FLEET_MYSQL_READ_REPLICA_MAX_OPEN_CONNS = "10"
FLEET_VULNERABILITIES_DATABASES_PATH = "/home/fleet"

View file

@ -3,6 +3,8 @@ package spec
import (
"fmt"
"strings"
"github.com/fleetdm/fleet/v4/server/platform/logging"
)
// DeprecatedKeyMapping defines a mapping from an old YAML key path to a new one.
@ -122,7 +124,9 @@ func migrateLeafKey(data map[string]any, oldKey, newKey, fullOldPath, fullNewPat
// Log deprecation warning
if logFn != nil {
logFn("[!] '%s' is deprecated; use '%s' instead\n", fullOldPath, fullNewPath)
if logging.TopicEnabled(logging.DeprecatedFieldTopic) {
logFn("[!] '%s' is deprecated; use '%s' instead\n", fullOldPath, fullNewPath)
}
}
// Copy value to new key and remove old key

View file

@ -11,10 +11,12 @@ import (
"fmt"
"os"
"regexp"
"sort"
"strings"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/endpointer"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/ghodss/yaml"
"github.com/hashicorp/go-multierror"
)
@ -48,20 +50,30 @@ type Metadata struct {
// rewriteNewToOldKeys uses RewriteDeprecatedKeys to rewrite new (renameto)
// key names back to old (json tag) names so that structs can be unmarshaled
// correctly when input uses the new key names.
func rewriteNewToOldKeys(raw json.RawMessage, target any) json.RawMessage {
func rewriteNewToOldKeys(raw json.RawMessage, target any) (json.RawMessage, map[string]string, error) {
rules := endpointer.ExtractAliasRules(target)
if len(rules) == 0 {
return raw
return raw, nil, nil
}
result, err := endpointer.RewriteDeprecatedKeys(raw, rules)
result, deprecatedKeysMap, err := endpointer.RewriteDeprecatedKeys(raw, rules)
if err != nil {
return raw // fall back to original on error
return nil, nil, err // fall back to original on error
}
return result
return result, deprecatedKeysMap, nil
}
type GroupFromBytesOpts struct {
LogFn func(format string, args ...any)
}
// GroupFromBytes parses a Group from concatenated YAML specs.
func GroupFromBytes(b []byte) (*Group, error) {
func GroupFromBytes(b []byte, options ...GroupFromBytesOpts) (*Group, error) {
// Get optional logger.
var logFn func(format string, args ...any)
if len(options) > 0 {
logFn = options[0].LogFn
}
specs := &Group{}
for _, specItem := range SplitYaml(string(b)) {
var s Metadata
@ -78,9 +90,14 @@ func GroupFromBytes(b []byte) (*Group, error) {
return nil, fmt.Errorf(`Missing required fields ("spec") on provided %q configuration.`, s.Kind)
}
var deprecatedKeysMap map[string]string
switch kind {
case fleet.QueryKind:
s.Spec = rewriteNewToOldKeys(s.Spec, fleet.QuerySpec{})
var err error
s.Spec, deprecatedKeysMap, err = rewriteNewToOldKeys(s.Spec, fleet.QuerySpec{})
if err != nil {
return nil, fmt.Errorf("in %s spec: %w", kind, err)
}
var querySpec *fleet.QuerySpec
if err := yaml.Unmarshal(s.Spec, &querySpec); err != nil {
return nil, fmt.Errorf("unmarshaling %s spec: %w", kind, err)
@ -88,7 +105,11 @@ func GroupFromBytes(b []byte) (*Group, error) {
specs.Queries = append(specs.Queries, querySpec)
case fleet.PackKind:
s.Spec = rewriteNewToOldKeys(s.Spec, fleet.PackSpec{})
var err error
s.Spec, deprecatedKeysMap, err = rewriteNewToOldKeys(s.Spec, fleet.PackSpec{})
if err != nil {
return nil, fmt.Errorf("in %s spec: %w", kind, err)
}
var packSpec *fleet.PackSpec
if err := yaml.Unmarshal(s.Spec, &packSpec); err != nil {
return nil, fmt.Errorf("unmarshaling %s spec: %w", kind, err)
@ -96,7 +117,11 @@ func GroupFromBytes(b []byte) (*Group, error) {
specs.Packs = append(specs.Packs, packSpec)
case fleet.LabelKind:
s.Spec = rewriteNewToOldKeys(s.Spec, fleet.LabelSpec{})
var err error
s.Spec, deprecatedKeysMap, err = rewriteNewToOldKeys(s.Spec, fleet.LabelSpec{})
if err != nil {
return nil, fmt.Errorf("in %s spec: %w", kind, err)
}
var labelSpec *fleet.LabelSpec
if err := yaml.Unmarshal(s.Spec, &labelSpec); err != nil {
return nil, fmt.Errorf("unmarshaling %s spec: %w", kind, err)
@ -104,7 +129,11 @@ func GroupFromBytes(b []byte) (*Group, error) {
specs.Labels = append(specs.Labels, labelSpec)
case fleet.PolicyKind:
s.Spec = rewriteNewToOldKeys(s.Spec, fleet.PolicySpec{})
var err error
s.Spec, deprecatedKeysMap, err = rewriteNewToOldKeys(s.Spec, fleet.PolicySpec{})
if err != nil {
return nil, fmt.Errorf("in %s spec: %w", kind, err)
}
var policySpec *fleet.PolicySpec
if err := yaml.Unmarshal(s.Spec, &policySpec); err != nil {
return nil, fmt.Errorf("unmarshaling %s spec: %w", kind, err)
@ -112,6 +141,11 @@ func GroupFromBytes(b []byte) (*Group, error) {
specs.Policies = append(specs.Policies, policySpec)
case fleet.AppConfigKind:
var err error
s.Spec, deprecatedKeysMap, err = rewriteNewToOldKeys(s.Spec, fleet.AppConfig{})
if err != nil {
return nil, fmt.Errorf("in %s spec: %w", kind, err)
}
if specs.AppConfig != nil {
return nil, errors.New("config defined twice in the same file")
}
@ -134,7 +168,11 @@ func GroupFromBytes(b []byte) (*Group, error) {
specs.EnrollSecret = enrollSecretSpec
case fleet.UserRolesKind:
s.Spec = rewriteNewToOldKeys(s.Spec, fleet.UsersRoleSpec{})
var err error
s.Spec, deprecatedKeysMap, err = rewriteNewToOldKeys(s.Spec, fleet.UsersRoleSpec{})
if err != nil {
return nil, fmt.Errorf("in %s spec: %w", kind, err)
}
var userRoleSpec *fleet.UsersRoleSpec
if err := yaml.Unmarshal(s.Spec, &userRoleSpec); err != nil {
return nil, fmt.Errorf("unmarshaling %s spec: %w", kind, err)
@ -154,6 +192,17 @@ func GroupFromBytes(b []byte) (*Group, error) {
default:
return nil, fmt.Errorf("unknown kind %q", s.Kind)
}
if logFn != nil && len(deprecatedKeysMap) > 0 && logging.TopicEnabled(logging.DeprecatedFieldTopic) {
oldKeys := make([]string, 0, len(deprecatedKeysMap))
for oldKey := range deprecatedKeysMap {
oldKeys = append(oldKeys, oldKey)
}
sort.Strings(oldKeys)
for _, oldKey := range oldKeys {
logFn(fmt.Sprintf("[!] In %s: `%s` is deprecated, please use `%s` instead.\n", kind, oldKey, deprecatedKeysMap[oldKey]))
}
}
}
return specs, nil
}

View file

@ -22,6 +22,7 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/license"
"github.com/fleetdm/fleet/v4/server/contexts/logging"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
platform_logging "github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/fleetdm/fleet/v4/server/platform/middleware/authzcheck"
"github.com/fleetdm/fleet/v4/server/platform/middleware/ratelimit"
"github.com/go-kit/kit/endpoint"
@ -304,11 +305,13 @@ func DecodeQueryTagValue(r *http.Request, fp fieldPair, customDecoder DomainQuer
}
}
// Log deprecation warning - the old name was used.
logging.WithLevel(ctx, slog.LevelWarn)
logging.WithExtras(ctx,
"deprecated_param", queryTagValue,
"deprecation_warning", fmt.Sprintf("'%s' is deprecated, use '%s' instead", queryTagValue, renameTo),
)
if platform_logging.TopicEnabled(platform_logging.DeprecatedFieldTopic) {
logging.WithLevel(ctx, slog.LevelWarn)
logging.WithExtras(ctx,
"deprecated_param", queryTagValue,
"deprecation_warning", fmt.Sprintf("'%s' is deprecated, use '%s' instead", queryTagValue, renameTo),
)
}
}
} else if renameTo, hasRenameTo := fp.Sf.Tag.Lookup("renameto"); hasRenameTo {
renameTo, _, err = ParseTag(renameTo)

View file

@ -17,7 +17,7 @@ type AliasConflictError struct {
}
func (e *AliasConflictError) Error() string {
return fmt.Sprintf("Conflicting field names: cannot specify both %q (deprecated) and %q in the same request", e.Old, e.New)
return fmt.Sprintf("Conflicting field names: cannot specify both `%s` (deprecated) and `%s` in the same request", e.Old, e.New)
}
// AliasRule defines a key-rename rule: the deprecated (old) key name and its
@ -119,9 +119,9 @@ func (r *JSONKeyRewriteReader) Read(p []byte) (int, error) {
// decoded into a struct with `renameto` tags — the rewriter in MakeDecoder
// won't have seen the inner fields, so this function can be called before the
// deferred unmarshal.
func RewriteDeprecatedKeys(data []byte, rules []AliasRule) ([]byte, error) {
func RewriteDeprecatedKeys(data []byte, rules []AliasRule) ([]byte, map[string]string, error) {
if len(rules) == 0 || len(data) == 0 {
return data, nil
return data, nil, nil
}
oldIdx := make(map[string]AliasRule, len(rules))
newIdx := make(map[string]AliasRule, len(rules))
@ -136,9 +136,13 @@ func RewriteDeprecatedKeys(data []byte, rules []AliasRule) ([]byte, error) {
}
var buf bytes.Buffer
if err := rw.rewrite(bytes.NewReader(data), &buf); err != nil {
return nil, err
return nil, nil, err
}
return buf.Bytes(), nil
deprecatedKeysMap := make(map[string]string, len(rw.usedDeprecated))
for k := range rw.usedDeprecated {
deprecatedKeysMap[k] = rw.oldKeyIndex[k].NewKey
}
return buf.Bytes(), deprecatedKeysMap, nil
}
// rewrite reads tokens from src, rewrites deprecated keys, checks for alias

View file

@ -41,6 +41,7 @@ func (h *TopicFilterHandler) Handle(ctx context.Context, r slog.Record) error {
if topic != "" && !TopicEnabled(topic) {
return nil
}
return h.base.Handle(ctx, r)
}

View file

@ -4,6 +4,8 @@ import (
"sync"
)
const DeprecatedFieldTopic = "deprecated-field-names"
// disabledTopics tracks which topics have been explicitly disabled.
// Topics are enabled by default — only topics in this map are disabled.
var (

View file

@ -28,6 +28,7 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/endpointer"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/fleetdm/fleet/v4/server/version"
"github.com/go-kit/log/level"
"golang.org/x/text/unicode/norm"
@ -367,12 +368,24 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
// json.RawMessage and wasn't processed by the request decoder's rewriter.
if rules := endpointer.ExtractAliasRules(fleet.AppConfig{}); len(rules) > 0 {
var err error
if p, err = endpointer.RewriteDeprecatedKeys(p, rules); err != nil {
var deprecatedKeysMap map[string]string
if p, deprecatedKeysMap, err = endpointer.RewriteDeprecatedKeys(p, rules); err != nil {
msg := "failed to decode app config"
// If it's an alias conflict error, return a user-friendly message about deprecated fields.
var aliasConflictErr *endpointer.AliasConflictError
if errors.As(err, &aliasConflictErr) {
msg = err.Error()
}
return nil, ctxerr.Wrap(ctx, &fleet.BadRequestError{
Message: "failed to decode app config",
Message: msg,
InternalErr: err,
})
}
if len(deprecatedKeysMap) > 0 {
for oldKey, newKey := range deprecatedKeysMap {
svc.logger.WarnContext(ctx, fmt.Sprintf("App config: `%s` is deprecated, please use `%s` instead", oldKey, newKey), "log_topic", logging.DeprecatedFieldTopic)
}
}
}
invalid := &fleet.InvalidArgumentError{}

View file

@ -16,6 +16,7 @@ import (
"github.com/fleetdm/fleet/v4/server/fleet"
eu "github.com/fleetdm/fleet/v4/server/platform/endpointer"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
platform_logging "github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/fleetdm/fleet/v4/server/service/middleware/auth"
"github.com/go-kit/kit/endpoint"
kithttp "github.com/go-kit/kit/transport/http"
@ -320,11 +321,13 @@ func parseMultipartForm(ctx context.Context, r *http.Request, maxMemory int64) e
teamIDs, teamIDPresent := r.Form["team_id"]
if teamIDPresent && len(teamIDs) > 0 {
teamID := teamIDs[0]
logging.WithExtras(ctx,
"deprecated_param", "team_id",
"deprecation_warning", "'team_id' is deprecated, use 'fleet_id' instead",
)
logging.WithLevel(ctx, slog.LevelWarn)
if platform_logging.TopicEnabled(platform_logging.DeprecatedFieldTopic) {
logging.WithExtras(ctx,
"deprecated_param", "team_id",
"deprecation_warning", "'team_id' is deprecated, use 'fleet_id' instead",
)
logging.WithLevel(ctx, slog.LevelWarn)
}
r.Form.Set("fleet_id", teamID)
r.Form.Del("team_id")
r.MultipartForm.Value["fleet_id"] = []string{teamID}