Clean up Gitops tests and add deprecation tests (#43039)

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

* Moves repeated empty mocks into a new `setupEmptyGitOpsMocks` method
* Adds new "deprecation" tests:
* In TestGitOpsFullGlobal, TestGitOpsFullTeam and
TestGitOpsFullGlobalAndTeam tests "kitchen sink" with both new and
deprecated keys
* Added keys and checks to verify `setup_experience`,
`apple_business_manager` and `volume_purchasing_program` configs
* Consolidated map of deprecated -> new GitOps keys in one place
This commit is contained in:
Scott Gress 2026-04-08 11:57:03 -05:00 committed by GitHub
parent e6357cfab5
commit 3ae98ee01d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1234 additions and 1571 deletions

View file

@ -118,50 +118,32 @@ func jsonFieldName(t reflect.Type, fieldName string) string {
return name
}
// aliasRules maps deprecated JSON key names (from `json` struct tags) to their
// new canonical names (from `renameto` struct tags). Used by replaceAliasKeys
// and yamlMarshalRenamed to rename keys in serialized output.
var aliasRules = map[string]string{
"available_teams": "available_fleets",
"default_team": "default_fleet",
"host_team_id": "host_fleet_id",
"inherited_query_count": "inherited_report_count",
"ios_team_id": "ios_fleet_id",
"ios_team": "ios_fleet",
"ipados_team_id": "ipados_fleet_id",
"ipados_team": "ipados_fleet",
"live_query_disabled": "live_reporting_disabled",
"live_query_results": "discard_reports_data",
"macos_team_id": "macos_fleet_id",
"macos_team": "macos_fleet",
"queries": "reports",
"query_count": "report_count",
"query_id": "report_id",
"query_ids": "report_ids",
"query_name": "report_name",
"query_report_cap": "report_cap",
"query_reports_disabled": "discard_reports_data",
"query_stats": "report_stats",
// Deliberately not aliasing "query" as it is used exclusively to refer to SQL in GitOps.
// "query": "report",
"scheduled_query_id": "scheduled_report_id",
"scheduled_query_name": "scheduled_report_name",
"team": "fleet",
"team_id": "fleet_id",
"team_ids_by_name": "fleet_ids_by_name",
"team_ids": "fleet_ids",
"team_name": "fleet_name",
"teams": "fleets",
// aliasRules maps deprecated JSON key names to their new canonical names.
// Used by replaceAliasKeys and yamlMarshalRenamed to rename keys in serialized output
// for generate_gitops, fleetctl get (via printSpec), and fleetctl apply.
// Derived entirely from spec.DeprecatedGitOpsKeyMappings using leaf key segments.
var aliasRules = buildAliasRules()
// MDM settings renames
"bootstrap_package": "macos_bootstrap_package",
"custom_settings": "configuration_profiles",
"enable_release_device_manually": "apple_enable_release_device_manually",
"macos_settings": "apple_settings",
"macos_setup": "setup_experience",
"macos_setup_assistant": "apple_setup_assistant",
"manual_agent_install": "macos_manual_agent_install",
"script": "macos_script",
func buildAliasRules() map[string]string {
rules := make(map[string]string)
for _, m := range spec.DeprecatedGitOpsKeyMappings {
// Take the last segment of the old and new paths as the leaf key names.
// Remove any array indicators (e.g. "[]") in case an array key is renamed.
oldLeaf := m.OldPath
if i := strings.LastIndex(oldLeaf, "."); i >= 0 {
oldLeaf = oldLeaf[i+1:]
}
oldLeaf = strings.TrimSuffix(oldLeaf, "[]")
newLeaf := m.NewPath
if i := strings.LastIndex(newLeaf, "."); i >= 0 {
newLeaf = newLeaf[i+1:]
}
newLeaf = strings.TrimSuffix(newLeaf, "[]")
rules[oldLeaf] = newLeaf
}
return rules
}
// Replace deprecated keys with their new canonical names.

File diff suppressed because it is too large Load diff

View file

@ -14,11 +14,11 @@ labels:
description: A manual label without hosts key
label_membership_type: manual
controls: # Controls added to "No team"
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: ./lib/macos-password.mobileconfig
windows_settings:
custom_settings:
configuration_profiles:
- path: ./lib/windows-screenlock.xml
scripts:
- path: ./lib/collect-fleetd-logs.sh
@ -28,10 +28,10 @@ controls: # Controls added to "No team"
enable: false
mode: ""
webhook_url: ""
macos_setup:
bootstrap_package: null
setup_experience:
macos_bootstrap_package: null
enable_end_user_authentication: false
macos_setup_assistant: null
apple_setup_assistant: null
macos_updates:
deadline: null
minimum_version: null
@ -39,7 +39,7 @@ controls: # Controls added to "No team"
windows_updates:
deadline_days: null
grace_period_days: null
queries:
reports:
- name: Scheduled query stats
description: Collect osquery performance stats directly from osquery
query: SELECT *,
@ -122,9 +122,9 @@ org_settings:
- 10728
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_report_cap: 2000
query_reports_disabled: false
live_reporting_disabled: false
report_cap: 2000
discard_reports_data: false
scripts_disabled: false
server_url: $FLEET_SERVER_URL
ai_features_disabled: true

View file

@ -0,0 +1,208 @@
# Test config
labels:
- name: a
description: A cool global label
query: SELECT 1 FROM osquery_info
label_membership_type: dynamic
- name: b
description: A fresh global label
label_membership_type: manual
hosts:
- host1
- host2
- name: d
description: A manual label without hosts key
label_membership_type: manual
controls: # Controls added to "No team"
macos_settings:
custom_settings:
- path: ./lib/macos-password.mobileconfig
windows_settings:
custom_settings:
- path: ./lib/windows-screenlock.xml
scripts:
- path: ./lib/collect-fleetd-logs.sh
enable_disk_encryption: false
windows_require_bitlocker_pin: false
macos_migration:
enable: false
mode: ""
webhook_url: ""
macos_setup:
bootstrap_package: null
enable_end_user_authentication: false
macos_setup_assistant: null
macos_updates:
deadline: null
minimum_version: null
windows_enabled_and_configured: true
windows_updates:
deadline_days: null
grace_period_days: null
queries:
- name: Scheduled query stats
description: Collect osquery performance stats directly from osquery
query: SELECT *,
(SELECT value from osquery_flags where name = 'pack_delimiter') AS delimiter
FROM osquery_schedule;
interval: 0
platform: darwin,linux,windows
min_osquery_version: all
observer_can_run: false
automations_enabled: false
logging: snapshot
labels_include_any:
- a
- b
- name: orbit_info
query: SELECT * from orbit_info;
interval: 0
platform: darwin,linux,windows
min_osquery_version: all
observer_can_run: false
automations_enabled: true
logging: snapshot
- name: osquery_info
query: SELECT * from osquery_info;
interval: 604800 # 1 week
platform: darwin,linux,windows,chrome
min_osquery_version: all
observer_can_run: false
automations_enabled: true
logging: snapshot
policies:
- name: 😊 Failing policy
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
labels_include_any:
- a
- name: Passing policy
platform: linux,windows,darwin,chrome
description: This policy should always pass.
resolution: There is no resolution for this policy.
query: SELECT 1;
labels_exclude_any:
- b
- name: No root logins (macOS, Linux)
platform: linux,darwin
query: SELECT 1 WHERE NOT EXISTS (SELECT * FROM last
WHERE username = "root"
AND time > (( SELECT unix_time FROM time ) - 3600 ))
critical: true
- name: 🔥 Failing policy
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
- name: 😊😊 Failing policy
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
agent_options:
command_line_flags:
distributed_denylist_duration: 0
config:
decorators:
load:
- SELECT uuid AS host_uuid FROM system_info;
- SELECT hostname AS hostname FROM system_info;
options:
disable_distributed: false
distributed_interval: 10
distributed_plugin: tls
distributed_tls_max_attempts: 3
logger_tls_endpoint: /api/v1/osquery/log
pack_delimiter: /
org_settings:
server_settings:
debug_host_ids:
- 10728
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_report_cap: 2000
query_reports_disabled: false
scripts_disabled: false
server_url: $FLEET_SERVER_URL
ai_features_disabled: true
org_info:
contact_url: https://fleetdm.com/company/contact
org_logo_url: ""
org_logo_url_light_background: ""
org_name: $ORG_NAME
smtp_settings:
authentication_method: authmethod_plain
authentication_type: authtype_username_password
configured: false
domain: ""
enable_smtp: false
enable_ssl_tls: true
enable_start_tls: true
password: ""
port: 587
sender_address: ""
server: ""
user_name: ""
verify_ssl_certs: true
sso_settings:
enable_jit_provisioning: false
enable_jit_role_sync: false
enable_sso: true
enable_sso_idp_login: false
entity_id: https://saml.example.com/entityid
idp_image_url: ""
idp_name: MockSAML
issuer_uri: ""
metadata: ""
metadata_url: https://mocksaml.com/api/saml/metadata
integrations:
jira: []
zendesk: []
google_calendar:
- domain: example.com
api_key_json: { "client_email": "service@example.com", "private_key": "google_calendar_private_key" }
mdm:
end_user_authentication:
entity_id: ""
idp_name: ""
issuer_uri: ""
metadata: ""
metadata_url: ""
webhook_settings:
activities_webhook:
enable_activities_webhook: true
destination_url: https://activities_webhook_url
failing_policies_webhook:
destination_url: https://host.docker.internal:8080/bozo
enable_failing_policies_webhook: false
host_batch_size: 0
policy_ids: []
host_status_webhook:
days_count: 0
destination_url: ""
enable_host_status_webhook: false
host_percentage: 0
interval: 24h0m0s
vulnerabilities_webhook:
destination_url: ""
enable_vulnerabilities_webhook: false
host_batch_size: 0
fleet_desktop: # Applies to Fleet Premium only
transparency_url: https://fleetdm.com/transparency
host_expiry_settings: # Applies to all teams
host_expiry_enabled: false
activity_expiry_settings:
activity_expiry_enabled: true
activity_expiry_window: 60
features: # Features added to all teams
enable_host_users: true
enable_software_inventory: true
vulnerability_settings:
databases_path: ""
secrets: # These secrets are used to enroll hosts to the "All teams" team
- secret: SampleSecret123
- secret: ABC
software:

Binary file not shown.

View file

@ -1,5 +1,5 @@
name: "${TEST_TEAM_NAME}"
team_settings:
settings:
secrets:
- secret: "SampleSecret123-team"
- secret: "ABC-team"
@ -35,11 +35,11 @@ agent_options:
logger_tls_endpoint: /api/v1/osquery/log
pack_delimiter: /
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: ./lib/macos-password.mobileconfig
windows_settings:
custom_settings:
configuration_profiles:
- path: ./lib/windows-screenlock.xml
scripts:
- path: ./lib/collect-fleetd-logs.sh
@ -49,10 +49,13 @@ controls:
enable: false
mode: ""
webhook_url: ""
macos_setup:
bootstrap_package: null
setup_experience:
macos_bootstrap_package: ${SOFTWARE_INSTALLER_URL}/signed.pkg
enable_end_user_authentication: false
macos_setup_assistant: null
apple_setup_assistant: null
apple_enable_release_device_manually: false
macos_script: ./lib/setup_script.sh
macos_manual_agent_install: false
macos_updates:
deadline: null
minimum_version: null
@ -60,7 +63,7 @@ controls:
windows_updates:
deadline_days: null
grace_period_days: null
queries:
reports:
- name: Scheduled query stats
description: Collect osquery performance stats directly from osquery
query: SELECT *,
@ -89,7 +92,7 @@ queries:
automations_enabled: true
logging: snapshot
policies:
- name: 😊 Failing policy
- name: "\U0001F60A Failing policy"
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
@ -110,12 +113,12 @@ policies:
WHERE username = "root"
AND time > (( SELECT unix_time FROM time ) - 3600 ))
critical: true
- name: 🔥 Failing policy
- name: "\U0001F525 Failing policy"
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
- name: 😊😊 Failing policy
- name: "\U0001F60A\U0001F60A Failing policy"
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.

View file

@ -0,0 +1,138 @@
name: "${TEST_TEAM_NAME}"
team_settings:
secrets:
- secret: "SampleSecret123-team"
- secret: "ABC-team"
webhook_settings:
host_status_webhook:
days_count: 14
destination_url: https://example.com/host_status_webhook
enable_host_status_webhook: true
host_percentage: 25
features:
enable_host_users: true
enable_software_inventory: true
host_expiry_settings:
host_expiry_enabled: true
host_expiry_window: 30
integrations:
google_calendar:
enable_calendar_events: true
webhook_url: https://example.com/google_calendar_webhook
agent_options:
command_line_flags:
distributed_denylist_duration: 0
config:
decorators:
load:
- SELECT uuid AS host_uuid FROM system_info;
- SELECT hostname AS hostname FROM system_info;
options:
disable_distributed: false
distributed_interval: 10
distributed_plugin: tls
distributed_tls_max_attempts: 3
logger_tls_endpoint: /api/v1/osquery/log
pack_delimiter: /
controls:
macos_settings:
custom_settings:
- path: ./lib/macos-password.mobileconfig
windows_settings:
custom_settings:
- path: ./lib/windows-screenlock.xml
scripts:
- path: ./lib/collect-fleetd-logs.sh
enable_disk_encryption: ${ENABLE_DISK_ENCRYPTION}
windows_require_bitlocker_pin: ${WINDOWS_REQUIRE_BITLOCKER_PIN}
macos_migration:
enable: false
mode: ""
webhook_url: ""
macos_setup:
bootstrap_package: ${SOFTWARE_INSTALLER_URL}/signed.pkg
enable_end_user_authentication: false
macos_setup_assistant: null
enable_release_device_manually: false
script: ./lib/setup_script.sh
manual_agent_install: false
macos_updates:
deadline: null
minimum_version: null
windows_enabled_and_configured: true
windows_updates:
deadline_days: null
grace_period_days: null
queries:
- name: Scheduled query stats
description: Collect osquery performance stats directly from osquery
query: SELECT *,
(SELECT value from osquery_flags where name = 'pack_delimiter') AS delimiter
FROM osquery_schedule;
interval: 0
platform: darwin,linux,windows
min_osquery_version: all
observer_can_run: false
automations_enabled: false
logging: snapshot
- name: orbit_info
query: SELECT * from orbit_info;
interval: 0
platform: darwin,linux,windows
min_osquery_version: all
observer_can_run: false
automations_enabled: true
logging: snapshot
- name: osquery_info
query: SELECT * from osquery_info;
interval: 604800 # 1 week
platform: darwin,linux,windows,chrome
min_osquery_version: all
observer_can_run: false
automations_enabled: true
logging: snapshot
policies:
- name: 😊 Failing policy
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
calendar_events_enabled: true
labels_exclude_any:
- a
- name: Passing policy
platform: linux,windows,darwin,chrome
description: This policy should always pass.
resolution: There is no resolution for this policy.
query: SELECT 1;
labels_include_any:
- b
- name: No root logins (macOS, Linux)
platform: linux,darwin
query: SELECT 1 WHERE NOT EXISTS (SELECT * FROM last
WHERE username = "root"
AND time > (( SELECT unix_time FROM time ) - 3600 ))
critical: true
- name: 🔥 Failing policy
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
- name: 😊😊 Failing policy
platform: linux
description: This policy should always fail.
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
software:
packages:
- url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
install_script:
path: lib/install_ruby.sh
pre_install_query:
path: lib/query_ruby.yml
post_install_script:
path: lib/post_install_ruby.sh
uninstall_script:
path: lib/uninstall_ruby.sh
- url: ${SOFTWARE_INSTALLER_URL}/other.deb
self_service: true

View file

@ -2,9 +2,13 @@ package fleetctl
import (
"bytes"
"context"
"io"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
@ -39,3 +43,234 @@ func noopExitErrHandler(c *cli.Context, err error) {}
func RunAppNoChecks(args []string) (*bytes.Buffer, error) {
return RunApp(args)
}
type gitopsTestNotFoundError struct{}
func (e *gitopsTestNotFoundError) Error() string { return "not found" }
func (e *gitopsTestNotFoundError) IsNotFound() bool { return true }
// setupEmptyGitOpsMocks sets all datastore mock functions needed by the gitops command to
// no-op defaults. Tests should override individual mocks after calling this to add tracking
// or return specific values.
func setupEmptyGitOpsMocks(ds *mock.Store) {
// App config
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
ds.SaveAppConfigFunc = func(ctx context.Context, config *fleet.AppConfig) error { return nil }
// Labels
ds.GetLabelSpecsFunc = func(ctx context.Context, filter fleet.TeamFilter) ([]*fleet.LabelSpec, error) {
return nil, nil
}
ds.ApplyLabelSpecsWithAuthorFunc = func(ctx context.Context, specs []*fleet.LabelSpec, authorID *uint) error {
return nil
}
ds.SetAsideLabelsFunc = func(ctx context.Context, teamID *uint, names []string, user fleet.User) error {
return nil
}
ds.LabelByNameFunc = func(ctx context.Context, name string, filter fleet.TeamFilter) (*fleet.Label, error) {
return &fleet.Label{ID: 1, Name: name}, nil
}
ds.DeleteLabelFunc = func(ctx context.Context, name string, filter fleet.TeamFilter) error { return nil }
ds.LabelsByNameFunc = func(ctx context.Context, names []string, filter fleet.TeamFilter) (map[string]*fleet.Label, error) {
return map[string]*fleet.Label{}, nil
}
ds.LabelIDsByNameFunc = func(ctx context.Context, names []string, filter fleet.TeamFilter) (map[string]uint, error) {
return map[string]uint{}, nil
}
// Secrets
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
return nil
}
ds.IsEnrollSecretAvailableFunc = func(ctx context.Context, secret string, isNew bool, teamID *uint) (bool, error) {
return true, nil
}
// MDM profiles
ds.BatchSetMDMProfilesFunc = func(
ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile,
macDecls []*fleet.MDMAppleDeclaration, androidProfiles []*fleet.MDMAndroidConfigProfile, vars []fleet.MDMProfileIdentifierFleetVariables,
) (fleet.MDMProfilesUpdates, error) {
return fleet.MDMProfilesUpdates{}, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(
ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string,
) (fleet.MDMProfilesUpdates, error) {
return fleet.MDMProfilesUpdates{}, nil
}
ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) {
return &fleet.MDMAppleDeclaration{}, nil
}
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
}
ds.SetOrUpdateMDMWindowsConfigProfileFunc = func(ctx context.Context, cp fleet.MDMWindowsConfigProfile) error {
return nil
}
ds.DeleteMDMWindowsConfigProfileByTeamAndNameFunc = func(ctx context.Context, teamID *uint, profileName string) error {
return nil
}
ds.NewMDMAppleConfigProfileFunc = func(ctx context.Context, p fleet.MDMAppleConfigProfile, usesFleetVars []fleet.FleetVarName) (*fleet.MDMAppleConfigProfile, error) {
return &fleet.MDMAppleConfigProfile{}, nil
}
ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) {
return &fleet.MDMAppleBootstrapPackage{}, nil
}
ds.DeleteMDMAppleBootstrapPackageFunc = func(ctx context.Context, teamID uint) error { return nil }
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {
return nil, &gitopsTestNotFoundError{}
}
ds.DeleteMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) error { return nil }
ds.InsertOrReplaceMDMConfigAssetFunc = func(ctx context.Context, asset fleet.MDMConfigAsset) error {
return nil
}
ds.HardDeleteMDMConfigAssetFunc = func(ctx context.Context, assetName fleet.MDMAssetName) error {
return nil
}
ds.DeleteMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName) error {
return nil
}
ds.MDMGetEULAMetadataFunc = func(ctx context.Context) (*fleet.MDMEULA, error) {
return nil, &gitopsTestNotFoundError{}
}
ds.MDMInsertEULAFunc = func(ctx context.Context, eula *fleet.MDMEULA) error { return nil }
ds.MDMDeleteEULAFunc = func(ctx context.Context, token string) error { return nil }
ds.ExpandEmbeddedSecretsAndUpdatedAtFunc = func(ctx context.Context, document string) (string, *time.Time, error) {
return document, nil, nil
}
// Scripts
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) ([]fleet.ScriptResponse, error) {
return []fleet.ScriptResponse{}, nil
}
// Policies and queries
ds.ListGlobalPoliciesFunc = func(ctx context.Context, opts fleet.ListOptions) ([]*fleet.Policy, error) {
return nil, nil
}
ds.ListTeamPoliciesFunc = func(
ctx context.Context, teamID uint, opts fleet.ListOptions, iopts fleet.ListOptions, automationFilter string,
) ([]*fleet.Policy, []*fleet.Policy, error) {
return nil, nil, nil
}
ds.ListQueriesFunc = func(ctx context.Context, opts fleet.ListQueryOptions) ([]*fleet.Query, int, int, *fleet.PaginationMetadata, error) {
return nil, 0, 0, nil, nil
}
ds.QueryByNameFunc = func(ctx context.Context, teamID *uint, name string) (*fleet.Query, error) {
return nil, &gitopsTestNotFoundError{}
}
// Teams
ds.ListTeamsFunc = func(ctx context.Context, filter fleet.TeamFilter, opt fleet.ListOptions) ([]*fleet.Team, error) {
return nil, nil
}
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
return nil, &gitopsTestNotFoundError{}
}
ds.TeamByFilenameFunc = func(ctx context.Context, filename string) (*fleet.Team, error) {
return nil, nil
}
ds.TeamLiteFunc = func(ctx context.Context, id uint) (*fleet.TeamLite, error) {
return &fleet.TeamLite{}, nil
}
ds.NewTeamFunc = func(ctx context.Context, newTeam *fleet.Team) (*fleet.Team, error) {
newTeam.ID = 1
return newTeam, nil
}
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
return team, nil
}
ds.DefaultTeamConfigFunc = func(ctx context.Context) (*fleet.TeamConfig, error) {
return &fleet.TeamConfig{}, nil
}
ds.SaveDefaultTeamConfigFunc = func(ctx context.Context, config *fleet.TeamConfig) error { return nil }
ds.ListHostsFunc = func(ctx context.Context, filter fleet.TeamFilter, opt fleet.HostListOptions) ([]*fleet.Host, error) {
return nil, nil
}
// Software
ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
return nil
}
ds.BatchSetInHouseAppsInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
return nil
}
ds.GetSoftwareInstallersFunc = func(ctx context.Context, tmID uint) ([]fleet.SoftwarePackageResponse, error) {
return nil, nil
}
ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
return nil, 0, nil, nil
}
ds.ListSoftwareAutoUpdateSchedulesFunc = func(ctx context.Context, teamID uint, source string, optionalFilter ...fleet.SoftwareAutoUpdateScheduleFilter) ([]fleet.SoftwareAutoUpdateSchedule, error) {
return []fleet.SoftwareAutoUpdateSchedule{}, nil
}
ds.GetSoftwareCategoryIDsFunc = func(ctx context.Context, names []string) ([]uint, error) {
return nil, nil
}
ds.GetTeamsWithInstallerByHashFunc = func(ctx context.Context, sha256, url string) (map[uint][]*fleet.ExistingSoftwareInstaller, error) {
return map[uint][]*fleet.ExistingSoftwareInstaller{}, nil
}
ds.DeleteIconsAssociatedWithTitlesWithoutInstallersFunc = func(ctx context.Context, teamID uint) error {
return nil
}
ds.DeleteSetupExperienceScriptFunc = func(ctx context.Context, teamID *uint) error { return nil }
// VPP / App Store
ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam, _ map[string]uint) (bool, error) {
return false, nil
}
ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error { return nil }
ds.GetVPPAppsFunc = func(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
return nil, nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
// ABM
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
ds.GetABMTokenCountFunc = func(ctx context.Context) (int, error) { return 0, nil }
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { return nil }
// Certificate authorities
ds.BatchApplyCertificateAuthoritiesFunc = func(ctx context.Context, ops fleet.CertificateAuthoritiesBatchOperations) error {
return nil
}
ds.GetGroupedCertificateAuthoritiesFunc = func(ctx context.Context, includeSecrets bool) (*fleet.GroupedCertificateAuthorities, error) {
return &fleet.GroupedCertificateAuthorities{}, nil
}
ds.ListCertificateAuthoritiesFunc = func(ctx context.Context) ([]*fleet.CertificateAuthoritySummary, error) {
return nil, nil
}
ds.GetCertificateTemplatesByTeamIDFunc = func(ctx context.Context, teamID uint, opts fleet.ListOptions) ([]*fleet.CertificateTemplateResponseSummary, *fleet.PaginationMetadata, error) {
return []*fleet.CertificateTemplateResponseSummary{}, &fleet.PaginationMetadata{}, nil
}
ds.BatchDeleteCertificateTemplatesFunc = func(ctx context.Context, ids []uint) (bool, error) {
return false, nil
}
ds.CreatePendingCertificateTemplatesForExistingHostsFunc = func(ctx context.Context, certificateTemplateID uint, teamID uint) (int64, error) {
return 0, nil
}
ds.SetHostCertificateTemplatesToPendingRemoveFunc = func(ctx context.Context, certTmplID uint) error {
return nil
}
// Conditional access
ds.ConditionalAccessMicrosoftGetFunc = func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) {
return &fleet.ConditionalAccessMicrosoftIntegration{}, nil
}
// Setup experience
ds.TeamIDsWithSetupExperienceIdPEnabledFunc = func(ctx context.Context) ([]uint, error) {
return nil, nil
}
// Jobs
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
return &fleet.Job{}, nil
}
}

View file

@ -231,6 +231,9 @@ func StartSoftwareInstallerServer(t *testing.T) {
// serve same content as ruby.deb
w.Header().Set("Content-Type", "application/vnd.debian.binary-package")
_, _ = w.Write(b)
case strings.HasSuffix(r.URL.Path, ".pkg"):
pkgDir := getPathRelative("../testdata/gitops/lib/")
http.ServeFile(w, r, filepath.Join(pkgDir, filepath.Base(r.URL.Path)))
default:
http.ServeFile(w, r, filepath.Join(baseDir, filepath.Base(r.URL.Path)))
}
@ -339,6 +342,9 @@ func SetupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
ds.DeleteMDMAppleBootstrapPackageFunc = func(ctx context.Context, teamID uint) error {
return nil
}
ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error {
return nil
}
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {
return nil, nil
}

View file

@ -614,8 +614,8 @@ func (s *enterpriseIntegrationGitopsTestSuite) TestCAIntegrations() {
_, err = globalFile.WriteString(fmt.Sprintf(`
agent_options:
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s/testdata/gitops/lib/scep-and-digicert.mobileconfig
org_settings:
server_settings:
@ -684,8 +684,8 @@ reports:
_, err = globalFile.WriteString(fmt.Sprintf(`
agent_options:
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s/testdata/gitops/lib/scep-and-digicert.mobileconfig
org_settings:
server_settings:
@ -741,8 +741,8 @@ reports:
_, err = globalFile.WriteString(`
agent_options:
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
org_settings:
server_settings:
server_url: $FLEET_URL
@ -933,8 +933,8 @@ labels:
- name: Label1
query: select 1
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s
%s
org_settings:
@ -955,8 +955,8 @@ reports:
`
teamTemplate = `
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s
%s
software:
@ -1265,8 +1265,8 @@ reports:
noTeamTemplate = `name: Unassigned
policies:
controls:
macos_setup:
script: %s
setup_experience:
macos_script: %s
software:
`
)
@ -1606,8 +1606,8 @@ func (s *enterpriseIntegrationGitopsTestSuite) TestRemoveCustomSettingsFromDefau
globalTemplateWithCustomSettings = `
agent_options:
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s
org_settings:
server_settings:
@ -1701,9 +1701,9 @@ reports:
globalConfigOnly = `
agent_options:
controls:
macos_setup:
bootstrap_package: %s
manual_agent_install: %t
setup_experience:
macos_bootstrap_package: %s
macos_manual_agent_install: %t
org_settings:
server_settings:
server_url: $FLEET_URL
@ -1716,18 +1716,18 @@ reports:
noTeamConfig = `name: Unassigned
controls:
macos_setup:
bootstrap_package: %s
manual_agent_install: true
setup_experience:
macos_bootstrap_package: %s
macos_manual_agent_install: true
policies:
software:
`
teamConfig = `
controls:
macos_setup:
bootstrap_package: %s
manual_agent_install: %t
setup_experience:
macos_bootstrap_package: %s
macos_manual_agent_install: %t
software:
reports:
policies:
@ -1902,8 +1902,8 @@ func (s *enterpriseIntegrationGitopsTestSuite) TestMacOSSetupScriptWithFleetSecr
const noTeamTemplate = `name: Unassigned
policies:
controls:
macos_setup:
script: %s
setup_experience:
macos_script: %s
software:
`
noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml")
@ -2013,8 +2013,8 @@ agent_options:
load:
- SELECT uuid AS host_uuid FROM system_info;
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s
reports: []
policies: []
@ -2149,8 +2149,8 @@ agent_options:
load:
- SELECT uuid AS host_uuid FROM system_info;
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s
reports:
policies:
@ -3733,9 +3733,9 @@ queries:
testVPP := `
controls:
macos_setup:
bootstrap_package: %s
manual_agent_install: true
setup_experience:
macos_bootstrap_package: %s
macos_manual_agent_install: true
software:
app_store_apps:
- app_store_id: "2"
@ -3758,9 +3758,9 @@ team_settings:
//nolint:gosec // test code
testPackages := `
controls:
macos_setup:
bootstrap_package: %s
manual_agent_install: true
setup_experience:
macos_bootstrap_package: %s
macos_manual_agent_install: true
software:
app_store_apps:
packages:
@ -3884,8 +3884,8 @@ org_settings:
- secret: test_secret
agent_options:
controls:
macos_settings:
custom_settings:
apple_settings:
configuration_profiles:
- path: %s
policies:
reports:

View file

@ -18,13 +18,41 @@ type DeprecatedKeyMapping struct {
NewPath string
}
// DeprecatedGitOpsKeyMappings defines all deprecated GitOps YAML key mappings.
// DeprecatedGitOpsKeyMappings is the single source of truth for all deprecated key renames.
// It serves two purposes:
// 1. ApplyDeprecatedKeyMappings uses the full paths to migrate deprecated keys in gitops YAML input.
// 2. buildAliasRules (in generate_gitops.go) extracts leaf key names to rename keys in
// serialized output for generate_gitops, fleetctl get, and fleetctl apply.
//
// When adding new deprecations, add them here.
var DeprecatedGitOpsKeyMappings = []DeprecatedKeyMapping{
// Top-level keys
// Top-level gitops keys
{"team_settings", "settings"},
{"queries", "reports"},
// Controls: macos_settings -> apple_settings (parent first, then children)
{"controls.macos_settings", "controls.apple_settings"},
{"controls.apple_settings.custom_settings", "controls.apple_settings.configuration_profiles"},
// Controls: windows_settings children
{"controls.windows_settings.custom_settings", "controls.windows_settings.configuration_profiles"},
// Controls: android_settings children
{"controls.android_settings.custom_settings", "controls.android_settings.configuration_profiles"},
// Controls: macos_setup -> setup_experience (parent first, then children)
{"controls.macos_setup", "controls.setup_experience"},
{"controls.setup_experience.bootstrap_package", "controls.setup_experience.macos_bootstrap_package"},
{"controls.setup_experience.macos_setup_assistant", "controls.setup_experience.apple_setup_assistant"},
{"controls.setup_experience.enable_release_device_manually", "controls.setup_experience.apple_enable_release_device_manually"},
{"controls.setup_experience.script", "controls.setup_experience.macos_script"},
{"controls.setup_experience.manual_agent_install", "controls.setup_experience.macos_manual_agent_install"},
// Org settings: server_settings
{"org_settings.server_settings.live_query_disabled", "org_settings.server_settings.live_reporting_disabled"},
{"org_settings.server_settings.query_reports_disabled", "org_settings.server_settings.discard_reports_data"},
{"org_settings.server_settings.query_report_cap", "org_settings.server_settings.report_cap"},
// Nested keys in org_settings.mdm.apple_business_manager[]
{"org_settings.mdm.apple_business_manager[].macos_team", "org_settings.mdm.apple_business_manager[].macos_fleet"},
{"org_settings.mdm.apple_business_manager[].ios_team", "org_settings.mdm.apple_business_manager[].ios_fleet"},
@ -32,6 +60,31 @@ var DeprecatedGitOpsKeyMappings = []DeprecatedKeyMapping{
// Nested keys in org_settings.mdm.volume_purchasing_program[]
{"org_settings.mdm.volume_purchasing_program[].teams", "org_settings.mdm.volume_purchasing_program[].fleets"},
// The following entries are renameto tags on struct fields that appear in serialized
// API output (fleetctl get and fleetctl apply) but are not gitops input keys. They are
// included here so that buildAliasRules can derive them. The paths are leaf-only (no dots)
// since they don't participate in ApplyDeprecatedKeyMappings traversal.
{"available_teams", "available_fleets"},
{"default_team", "default_fleet"},
{"host_team_id", "host_fleet_id"},
{"inherited_query_count", "inherited_report_count"},
{"ios_team_id", "ios_fleet_id"},
{"ipados_team_id", "ipados_fleet_id"},
{"live_query_results", "live_report_results"},
{"macos_team_id", "macos_fleet_id"},
{"query_count", "report_count"},
{"query_id", "report_id"},
{"query_ids", "report_ids"},
{"query_name", "report_name"},
{"query_stats", "report_stats"},
{"scheduled_query_id", "scheduled_report_id"},
{"scheduled_query_name", "scheduled_report_name"},
{"team", "fleet"},
{"team_id", "fleet_id"},
{"team_ids_by_name", "fleet_ids_by_name"},
{"team_ids", "fleet_ids"},
{"team_name", "fleet_name"},
}
// ApplyDeprecatedKeyMappings walks the YAML data map and migrates deprecated keys to their new names.

View file

@ -257,10 +257,10 @@ func TestValidGitOpsYaml(t *testing.T) {
// Check org settings
serverSettings, ok := gitops.OrgSettings["server_settings"]
assert.True(t, ok, "server_settings not found")
assert.Equal(t, "https://fleet.example.com", serverSettings.(map[string]interface{})["server_url"])
assert.EqualValues(t, 2000, serverSettings.(map[string]interface{})["query_report_cap"])
assert.Equal(t, "https://fleet.example.com", serverSettings.(map[string]any)["server_url"])
assert.EqualValues(t, 2000, serverSettings.(map[string]any)["report_cap"])
assert.Contains(t, gitops.OrgSettings, "org_info")
orgInfo, ok := gitops.OrgSettings["org_info"].(map[string]interface{})
orgInfo, ok := gitops.OrgSettings["org_info"].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "Fleet Device Management", orgInfo["org_name"])
assert.Contains(t, gitops.OrgSettings, "smtp_settings")
@ -2960,9 +2960,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "apple_settings")
require.Contains(t, err.Error(), "`macos_settings` (deprecated)")
require.Contains(t, err.Error(), "'controls.macos_settings' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_apple_custom_settings", func(t *testing.T) {
@ -2989,9 +2989,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "configuration_profiles")
require.Contains(t, err.Error(), "`custom_settings` (deprecated)")
require.Contains(t, err.Error(), "'controls.apple_settings.custom_settings' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_windows_custom_settings", func(t *testing.T) {
@ -3018,9 +3018,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "configuration_profiles")
require.Contains(t, err.Error(), "`custom_settings` (deprecated)")
require.Contains(t, err.Error(), "'controls.windows_settings.custom_settings' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_android_custom_settings", func(t *testing.T) {
@ -3047,9 +3047,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "configuration_profiles")
require.Contains(t, err.Error(), "`custom_settings` (deprecated)")
require.Contains(t, err.Error(), "'controls.android_settings.custom_settings' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_setup_experience", func(t *testing.T) {
@ -3073,9 +3073,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "setup_experience")
require.Contains(t, err.Error(), "`macos_setup` (deprecated)")
require.Contains(t, err.Error(), "'controls.macos_setup' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_bootstrap_package", func(t *testing.T) {
@ -3098,9 +3098,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "macos_bootstrap_package")
require.Contains(t, err.Error(), "`bootstrap_package` (deprecated)")
require.Contains(t, err.Error(), "'controls.setup_experience.bootstrap_package' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_enable_release_device_manually", func(t *testing.T) {
@ -3123,9 +3123,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "apple_enable_release_device_manually")
require.Contains(t, err.Error(), "`enable_release_device_manually` (deprecated)")
require.Contains(t, err.Error(), "'controls.setup_experience.enable_release_device_manually' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_script", func(t *testing.T) {
@ -3148,9 +3148,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "macos_script")
require.Contains(t, err.Error(), "`script` (deprecated)")
require.Contains(t, err.Error(), "'controls.setup_experience.script' (deprecated)")
})
t.Run("duplicate_old_and_new_keys_error_manual_agent_install", func(t *testing.T) {
@ -3173,9 +3173,9 @@ controls:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "macos_manual_agent_install")
require.Contains(t, err.Error(), "`manual_agent_install` (deprecated)")
require.Contains(t, err.Error(), "'controls.setup_experience.manual_agent_install' (deprecated)")
})
t.Run("duplicate_keys_external_file", func(t *testing.T) {
@ -3206,7 +3206,7 @@ org_settings:
_, err := GitOpsFromFile(yamlPath, dir, nil, nopLogf)
require.Error(t, err)
require.Contains(t, err.Error(), "Conflicting field names")
require.Contains(t, err.Error(), "cannot specify both")
require.Contains(t, err.Error(), "apple_settings")
require.Contains(t, err.Error(), "`macos_settings` (deprecated)")
})

View file

@ -3,9 +3,9 @@ server_settings:
- 10728
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_report_cap: 2000
query_reports_disabled: false
live_reporting_disabled: false
report_cap: 2000
discard_reports_data: false
scripts_disabled: false
server_url: https://fleet.example.com
org_info: