diff --git a/changes/22385-cli-gitops-macos-setup-software-and-script b/changes/22385-cli-gitops-macos-setup-software-and-script
new file mode 100644
index 000000000000..09ea4359f317
--- /dev/null
+++ b/changes/22385-cli-gitops-macos-setup-software-and-script
@@ -0,0 +1 @@
+* Added support to `fleetctl gitops` to specify a setup experience script to run and software to install, for a team or no team.
diff --git a/cmd/fleetctl/apply.go b/cmd/fleetctl/apply.go
index 4e29f15b18b6..237c46ab0b2f 100644
--- a/cmd/fleetctl/apply.go
+++ b/cmd/fleetctl/apply.go
@@ -94,7 +94,7 @@ func applyCommand() *cli.Command {
teamsSoftwareInstallers := make(map[string][]fleet.SoftwarePackageResponse)
teamsScripts := make(map[string][]fleet.ScriptResponse)
- _, _, _, err = fleetClient.ApplyGroup(c.Context, specs, baseDir, logf, nil, opts, teamsSoftwareInstallers, teamsScripts)
+ _, _, _, err = fleetClient.ApplyGroup(c.Context, false, specs, baseDir, logf, nil, opts, teamsSoftwareInstallers, teamsScripts)
if err != nil {
return err
}
diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go
index 22f34287b0b9..cb396e10472d 100644
--- a/cmd/fleetctl/gitops_test.go
+++ b/cmd/fleetctl/gitops_test.go
@@ -438,6 +438,9 @@ func TestGitOpsBasicTeam(t *testing.T) {
ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
return nil, 0, nil, nil
}
+ ds.DeleteSetupExperienceScriptFunc = func(ctx context.Context, teamID *uint) error {
+ return nil
+ }
tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@@ -903,6 +906,9 @@ func TestGitOpsFullTeam(t *testing.T) {
ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
return nil, 0, nil, nil
}
+ ds.DeleteSetupExperienceScriptFunc = func(ctx context.Context, teamID *uint) error {
+ return nil
+ }
startSoftwareInstallerServer(t)
@@ -1159,6 +1165,9 @@ func TestGitOpsBasicGlobalAndTeam(t *testing.T) {
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
+ ds.DeleteSetupExperienceScriptFunc = func(ctx context.Context, teamID *uint) error {
+ return nil
+ }
globalFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@@ -1437,6 +1446,9 @@ func TestGitOpsBasicGlobalAndNoTeam(t *testing.T) {
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
+ ds.DeleteSetupExperienceScriptFunc = func(ctx context.Context, teamID *uint) error {
+ return nil
+ }
globalFileBasic, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@@ -2354,6 +2366,9 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
+ ds.DeleteSetupExperienceScriptFunc = func(ctx context.Context, teamID *uint) error {
+ return nil
+ }
t.Setenv("FLEET_SERVER_URL", fleetServerURL)
t.Setenv("ORG_NAME", orgName)
diff --git a/cmd/fleetctl/preview.go b/cmd/fleetctl/preview.go
index b009c0ca702b..cf5b28039f7c 100644
--- a/cmd/fleetctl/preview.go
+++ b/cmd/fleetctl/preview.go
@@ -389,7 +389,7 @@ Use the stop and reset subcommands to manage the server and dependencies once st
// so pass in the current working directory.
teamsSoftwareInstallers := make(map[string][]fleet.SoftwarePackageResponse)
teamsScripts := make(map[string][]fleet.ScriptResponse)
- _, _, _, err = client.ApplyGroup(c.Context, specs, ".", logf, nil, fleet.ApplyClientSpecOptions{}, teamsSoftwareInstallers, teamsScripts)
+ _, _, _, err = client.ApplyGroup(c.Context, false, specs, ".", logf, nil, fleet.ApplyClientSpecOptions{}, teamsSoftwareInstallers, teamsScripts)
if err != nil {
return err
}
diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
index ce6017b7da88..c3779641775a 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
+++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
@@ -130,7 +130,9 @@
"bootstrap_package": null,
"enable_end_user_authentication": false,
"macos_setup_assistant": null,
- "enable_release_device_manually": false
+ "enable_release_device_manually": false,
+ "script": null,
+ "software": null
},
"windows_settings": {
"custom_settings": null
diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
index 3637ccc5d74c..ff6fbaa22eae 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
@@ -50,6 +50,8 @@ spec:
enable_end_user_authentication: false
enable_release_device_manually: false
macos_setup_assistant:
+ script:
+ software:
windows_settings:
custom_settings: null
end_user_authentication:
diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
index 598e99c53e58..dea76a995b16 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
+++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
@@ -82,7 +82,9 @@
"bootstrap_package": null,
"enable_end_user_authentication": false,
"macos_setup_assistant": null,
- "enable_release_device_manually": false
+ "enable_release_device_manually": false,
+ "script": null,
+ "software": null
},
"windows_settings": {
"custom_settings": null
diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
index 56d2d13eac93..f6b8136407cd 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
@@ -50,6 +50,8 @@ spec:
enable_end_user_authentication: false
enable_release_device_manually: false
macos_setup_assistant:
+ script:
+ software:
windows_settings:
custom_settings:
end_user_authentication:
diff --git a/cmd/fleetctl/testdata/expectedGetTeamsJson.json b/cmd/fleetctl/testdata/expectedGetTeamsJson.json
index de6669ac1259..2e38e6b7e7a8 100644
--- a/cmd/fleetctl/testdata/expectedGetTeamsJson.json
+++ b/cmd/fleetctl/testdata/expectedGetTeamsJson.json
@@ -54,7 +54,9 @@
"bootstrap_package": null,
"enable_end_user_authentication": false,
"macos_setup_assistant": null,
- "enable_release_device_manually": false
+ "enable_release_device_manually": false,
+ "script": null,
+ "software": null
},
"windows_settings": {
"custom_settings": null
@@ -137,7 +139,9 @@
"bootstrap_package": null,
"enable_end_user_authentication": false,
"macos_setup_assistant": null,
- "enable_release_device_manually": false
+ "enable_release_device_manually": false,
+ "script": null,
+ "software": null
},
"windows_settings": {
"custom_settings": null
diff --git a/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml b/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml
index 422456f5b54e..fe37b4161566 100644
--- a/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml
@@ -34,6 +34,8 @@ spec:
enable_end_user_authentication: false
enable_release_device_manually: false
macos_setup_assistant:
+ script:
+ software:
scripts: null
secrets: null
webhook_settings:
@@ -84,6 +86,8 @@ spec:
enable_end_user_authentication: false
enable_release_device_manually: false
macos_setup_assistant:
+ script:
+ software:
scripts: null
webhook_settings:
host_status_webhook: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
index a158f27c1b52..49c129df695b 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
@@ -40,6 +40,8 @@ spec:
enable_end_user_authentication: false
macos_setup_assistant: null
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
index 4d9ffe1f50a0..27e6a2a5459e 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
@@ -40,6 +40,8 @@ spec:
enable_end_user_authentication: false
macos_setup_assistant: %s
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml
index f0aa275b56b3..7c8d9bb4865d 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml
@@ -22,6 +22,8 @@ spec:
enable_end_user_authentication: false
macos_setup_assistant: null
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
@@ -62,6 +64,8 @@ spec:
bootstrap_package: null
macos_setup_assistant: null
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml
index 1d2b07674017..45203bc28362 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml
@@ -22,6 +22,8 @@ spec:
enable_end_user_authentication: false
macos_setup_assistant: %s
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
@@ -62,6 +64,8 @@ spec:
bootstrap_package: %s
macos_setup_assistant: %s
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml
index 885d25482758..5e92912a19f1 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml
@@ -20,6 +20,8 @@ spec:
enable_end_user_authentication: false
macos_setup_assistant: null
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml
index 76c45b10b2f2..2c9da92d8fe8 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml
@@ -21,6 +21,8 @@ spec:
enable_end_user_authentication: false
macos_setup_assistant: %s
enable_release_device_manually: false
+ script: null
+ software: null
macos_updates:
deadline: null
minimum_version: null
diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go
index f06260e3043f..045fa48ccdbf 100644
--- a/ee/server/service/software_installers.go
+++ b/ee/server/service/software_installers.go
@@ -1301,15 +1301,16 @@ func (svc *Service) softwareBatchUpload(
}
installer := &fleet.UploadSoftwareInstallerPayload{
- TeamID: teamID,
- InstallScript: p.InstallScript,
- PreInstallQuery: p.PreInstallQuery,
- PostInstallScript: p.PostInstallScript,
- UninstallScript: p.UninstallScript,
- InstallerFile: bytes.NewReader(bodyBytes),
- SelfService: p.SelfService,
- UserID: userID,
- URL: p.URL,
+ TeamID: teamID,
+ InstallScript: p.InstallScript,
+ PreInstallQuery: p.PreInstallQuery,
+ PostInstallScript: p.PostInstallScript,
+ UninstallScript: p.UninstallScript,
+ InstallerFile: bytes.NewReader(bodyBytes),
+ SelfService: p.SelfService,
+ UserID: userID,
+ URL: p.URL,
+ InstallDuringSetup: p.InstallDuringSetup,
}
// set the filename before adding metadata, as it is used as fallback
diff --git a/ee/server/service/teams.go b/ee/server/service/teams.go
index 448abc367401..0952b972f336 100644
--- a/ee/server/service/teams.go
+++ b/ee/server/service/teams.go
@@ -1396,6 +1396,15 @@ func (svc *Service) editTeamFromSpec(
}
}
+ // if the setup experience script was cleared, remove it for that team
+ if spec.MDM.MacOSSetup.Script.Set &&
+ spec.MDM.MacOSSetup.Script.Value == "" &&
+ oldMacOSSetup.Script.Value != "" {
+ if err := svc.DeleteSetupExperienceScript(ctx, &team.ID); err != nil {
+ return ctxerr.Wrapf(ctx, err, "clear setup experience script for team %d", team.ID)
+ }
+ }
+
if didUpdateMacOSEndUserAuth {
if err := svc.updateMacOSSetupEnableEndUserAuth(
ctx, spec.MDM.MacOSSetup.EnableEndUserAuthentication, &team.ID, &team.Name,
diff --git a/ee/server/service/vpp.go b/ee/server/service/vpp.go
index d1f13bd55589..dc1c1c461e74 100644
--- a/ee/server/service/vpp.go
+++ b/ee/server/service/vpp.go
@@ -79,9 +79,10 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
SelfService: false,
Platform: fleet.IPadOSPlatform,
}, {
- AppStoreID: payload.AppStoreID,
- SelfService: payload.SelfService,
- Platform: fleet.MacOSPlatform,
+ AppStoreID: payload.AppStoreID,
+ SelfService: payload.SelfService,
+ Platform: fleet.MacOSPlatform,
+ InstallDuringSetup: payload.InstallDuringSetup,
}}...)
}
@@ -101,7 +102,14 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
return fleet.NewInvalidArgumentError("app_store_apps.platform",
fmt.Sprintf("platform must be one of '%s', '%s', or '%s", fleet.IOSPlatform, fleet.IPadOSPlatform, fleet.MacOSPlatform))
}
- vppAppTeams = append(vppAppTeams, fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: payload.AppStoreID, Platform: payload.Platform}, SelfService: payload.SelfService})
+ vppAppTeams = append(vppAppTeams, fleet.VPPAppTeam{
+ VPPAppID: fleet.VPPAppID{
+ AdamID: payload.AppStoreID,
+ Platform: payload.Platform,
+ },
+ SelfService: payload.SelfService,
+ InstallDuringSetup: payload.InstallDuringSetup,
+ })
}
var missingAssets []string
@@ -374,14 +382,15 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
func getVPPAppsMetadata(ctx context.Context, ids []fleet.VPPAppTeam) ([]*fleet.VPPApp, error) {
var apps []*fleet.VPPApp
- // Map of adamID to platform, then to whether it's available as self-service.
- adamIDMap := make(map[string]map[fleet.AppleDevicePlatform]bool)
+ // Map of adamID to platform, then to whether it's available as self-service
+ // and installed during setup.
+ adamIDMap := make(map[string]map[fleet.AppleDevicePlatform]fleet.VPPAppTeam)
for _, id := range ids {
if _, ok := adamIDMap[id.AdamID]; !ok {
- adamIDMap[id.AdamID] = make(map[fleet.AppleDevicePlatform]bool, 1)
- adamIDMap[id.AdamID][id.Platform] = id.SelfService
+ adamIDMap[id.AdamID] = make(map[fleet.AppleDevicePlatform]fleet.VPPAppTeam, 1)
+ adamIDMap[id.AdamID][id.Platform] = fleet.VPPAppTeam{SelfService: id.SelfService, InstallDuringSetup: id.InstallDuringSetup}
} else {
- adamIDMap[id.AdamID][id.Platform] = id.SelfService
+ adamIDMap[id.AdamID][id.Platform] = fleet.VPPAppTeam{SelfService: id.SelfService, InstallDuringSetup: id.InstallDuringSetup}
}
}
@@ -397,14 +406,15 @@ func getVPPAppsMetadata(ctx context.Context, ids []fleet.VPPAppTeam) ([]*fleet.V
for adamID, metadata := range assetMetatada {
platforms := getPlatformsFromSupportedDevices(metadata.SupportedDevices)
for platform := range platforms {
- if selfService, ok := adamIDMap[adamID][platform]; ok {
+ if props, ok := adamIDMap[adamID][platform]; ok {
app := &fleet.VPPApp{
VPPAppTeam: fleet.VPPAppTeam{
VPPAppID: fleet.VPPAppID{
AdamID: adamID,
Platform: platform,
},
- SelfService: selfService,
+ SelfService: props.SelfService,
+ InstallDuringSetup: props.InstallDuringSetup,
},
BundleIdentifier: metadata.BundleID,
IconURL: metadata.ArtworkURL,
diff --git a/pkg/spec/gitops.go b/pkg/spec/gitops.go
index f7aefa97d192..7ce55900f9d4 100644
--- a/pkg/spec/gitops.go
+++ b/pkg/spec/gitops.go
@@ -757,7 +757,8 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
for _, item := range software.Packages {
var softwarePackageSpec fleet.SoftwarePackageSpec
if item.Path != nil {
- fileBytes, err := os.ReadFile(resolveApplyRelativePath(baseDir, *item.Path))
+ softwarePackageSpec.ReferencedYamlPath = resolveApplyRelativePath(baseDir, *item.Path)
+ fileBytes, err := os.ReadFile(softwarePackageSpec.ReferencedYamlPath)
if err != nil {
multiError = multierror.Append(multiError, fmt.Errorf("failed to read policies file %s: %v", *item.Path, err))
continue
diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql
index a26d3a46915a..0c89a8961d65 100644
--- a/server/datastore/mysql/schema.sql
+++ b/server/datastore/mysql/schema.sql
@@ -65,7 +65,7 @@ CREATE TABLE `app_config_json` (
UNIQUE KEY `id` (`id`)
) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
-INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
+INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `calendar_events` (
diff --git a/server/datastore/mysql/setup_experience.go b/server/datastore/mysql/setup_experience.go
index 6628ae00a51a..22b3857efb9e 100644
--- a/server/datastore/mysql/setup_experience.go
+++ b/server/datastore/mysql/setup_experience.go
@@ -85,7 +85,7 @@ WHERE global_or_team_id = ?`
if err != nil {
return ctxerr.Wrap(ctx, err, "retrieving number of inserted software installers")
}
- totalInsertions += uint(inserts)
+ totalInsertions += uint(inserts) // nolint: gosec
// VPP apps
res, err = tx.ExecContext(ctx, stmtVPPApps, hostUUID, teamID)
@@ -96,7 +96,7 @@ WHERE global_or_team_id = ?`
if err != nil {
return ctxerr.Wrap(ctx, err, "retrieving number of inserted vpp apps")
}
- totalInsertions += uint(inserts)
+ totalInsertions += uint(inserts) // nolint: gosec
// Scripts
res, err = tx.ExecContext(ctx, stmtSetupScripts, hostUUID, teamID)
@@ -107,7 +107,7 @@ WHERE global_or_team_id = ?`
if err != nil {
return ctxerr.Wrap(ctx, err, "retrieving number of inserted setup experience scripts")
}
- totalInsertions += uint(inserts)
+ totalInsertions += uint(inserts) // nolint: gosec
if err := setHostAwaitingConfiguration(ctx, tx, hostUUID, true); err != nil {
return ctxerr.Wrap(ctx, err, "setting host awaiting configuration to true")
@@ -403,7 +403,7 @@ func (ds *Datastore) SetSetupExperienceScript(ctx context.Context, script *fleet
id, _ := scRes.LastInsertId()
// then create the script entity
- _, err = insertSetupExperienceScript(ctx, tx, script, uint(id))
+ _, err = insertSetupExperienceScript(ctx, tx, script, uint(id)) // nolint: gosec
return err
})
diff --git a/server/datastore/mysql/setup_experience_test.go b/server/datastore/mysql/setup_experience_test.go
index baf94153d00e..6ae5af22498f 100644
--- a/server/datastore/mysql/setup_experience_test.go
+++ b/server/datastore/mysql/setup_experience_test.go
@@ -186,13 +186,13 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
HostUUID: hostTeam1,
Name: "script1",
Status: "pending",
- ScriptID: nullableUint(uint(script1ID)),
+ ScriptID: nullableUint(uint(script1ID)), // nolint: gosec
},
{
HostUUID: hostTeam2,
Name: "script2",
Status: "pending",
- ScriptID: nullableUint(uint(script2ID)),
+ ScriptID: nullableUint(uint(script2ID)), // nolint: gosec
},
} {
var found bool
@@ -267,7 +267,7 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
HostUUID: hostTeam1,
Name: "script1",
Status: "pending",
- ScriptID: nullableUint(uint(script1ID)),
+ ScriptID: nullableUint(uint(script1ID)), // nolint: gosec
},
} {
var found bool
@@ -303,7 +303,7 @@ type setupExperienceInsertTestRows struct {
}
func nullableUint(val uint) sql.NullInt64 {
- return sql.NullInt64{Int64: int64(val), Valid: true}
+ return sql.NullInt64{Int64: int64(val), Valid: true} // nolint: gosec
}
func testGetSetupExperienceTitles(t *testing.T, ds *Datastore) {
@@ -668,7 +668,7 @@ func testSetupExperienceStatusResults(t *testing.T, ds *Datastore) {
require.NoError(t, err)
id, err := res.LastInsertId()
require.NoError(t, err)
- scriptID = uint(id)
+ scriptID = uint(id) // nolint: gosec
return nil
})
@@ -680,7 +680,7 @@ func testSetupExperienceStatusResults(t *testing.T, ds *Datastore) {
require.NoError(t, err)
id, err := res.LastInsertId()
require.NoError(t, err)
- sesr.ID = uint(id)
+ sesr.ID = uint(id) // nolint: gosec
return nil
})
}
diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go
index 95717e0638a3..04069f9bf780 100644
--- a/server/datastore/mysql/software_installers.go
+++ b/server/datastore/mysql/software_installers.go
@@ -847,16 +847,6 @@ WHERE
team_id = ?
`
- const countInstallDuringSetupAllInstallersInTeam = `
-SELECT
- COUNT(*)
-FROM
- software_installers
-WHERE
- global_or_team_id = ? AND
- install_during_setup = 1
-`
-
const deleteAllInstallersInTeam = `
DELETE FROM
software_installers
@@ -881,8 +871,8 @@ WHERE
SELECT
COUNT(*)
FROM
- software_installers
-WHERE
+ software_installers
+WHERE
global_or_team_id = ? AND
title_id NOT IN (?) AND
install_during_setup = 1
@@ -926,11 +916,12 @@ INSERT INTO software_installers (
user_name,
user_email,
url,
- package_ids
+ package_ids,
+ install_during_setup
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
(SELECT id FROM software_titles WHERE name = ? AND source = ? AND browser = ''),
- ?, (SELECT name FROM users WHERE id = ?), (SELECT email FROM users WHERE id = ?), ?, ?
+ ?, (SELECT name FROM users WHERE id = ?), (SELECT email FROM users WHERE id = ?), ?, ?, COALESCE(?, false)
)
ON DUPLICATE KEY UPDATE
install_script_content_id = VALUES(install_script_content_id),
@@ -946,7 +937,8 @@ ON DUPLICATE KEY UPDATE
user_id = VALUES(user_id),
user_name = VALUES(user_name),
user_email = VALUES(user_email),
- url = VALUES(url)
+ url = VALUES(url),
+ install_during_setup = COALESCE(?, install_during_setup)
`
// use a team id of 0 if no-team
@@ -955,20 +947,19 @@ ON DUPLICATE KEY UPDATE
globalOrTeamID = *tmID
}
+ // if we're batch-setting installers and replacing the ones installed during
+ // setup in the same go, no need to validate that we don't delete one marked
+ // as install during setup (since we're overwriting those). This is always
+ // called from fleetctl gitops, so it should always be the case anyway.
+ var replacingInstallDuringSetup bool
+ if len(installers) == 0 || installers[0].InstallDuringSetup != nil {
+ replacingInstallDuringSetup = true
+ }
+
if err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
// if no installers are provided, just delete whatever was in
// the table
if len(installers) == 0 {
- // check if any existing installer is install_during_setup, fail if there is one
- // TODO: will need to be reconciled with https://github.com/fleetdm/fleet/issues/22385
- var countInstallDuringSetup int
- if err := sqlx.GetContext(ctx, tx, &countInstallDuringSetup, countInstallDuringSetupAllInstallersInTeam, globalOrTeamID); err != nil {
- return ctxerr.Wrap(ctx, err, "check installers installed during setup")
- }
- if countInstallDuringSetup > 0 {
- return errDeleteInstallerInstalledDuringSetup
- }
-
if _, err := tx.ExecContext(ctx, unsetAllInstallersFromPolicies, globalOrTeamID); err != nil {
return ctxerr.Wrap(ctx, err, "unset all obsolete installers in policies")
}
@@ -1005,17 +996,18 @@ ON DUPLICATE KEY UPDATE
}
// check if any in the list are install_during_setup, fail if there is one
- // TODO: will need to be reconciled with https://github.com/fleetdm/fleet/issues/22385
- stmt, args, err = sqlx.In(countInstallDuringSetupNotInList, globalOrTeamID, titleIDs)
- if err != nil {
- return ctxerr.Wrap(ctx, err, "build statement to check installers install_during_setup")
- }
- var countInstallDuringSetup int
- if err := sqlx.GetContext(ctx, tx, &countInstallDuringSetup, stmt, args...); err != nil {
- return ctxerr.Wrap(ctx, err, "check installers installed during setup")
- }
- if countInstallDuringSetup > 0 {
- return errDeleteInstallerInstalledDuringSetup
+ if !replacingInstallDuringSetup {
+ stmt, args, err = sqlx.In(countInstallDuringSetupNotInList, globalOrTeamID, titleIDs)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "build statement to check installers install_during_setup")
+ }
+ var countInstallDuringSetup int
+ if err := sqlx.GetContext(ctx, tx, &countInstallDuringSetup, stmt, args...); err != nil {
+ return ctxerr.Wrap(ctx, err, "check installers installed during setup")
+ }
+ if countInstallDuringSetup > 0 {
+ return errDeleteInstallerInstalledDuringSetup
+ }
}
stmt, args, err = sqlx.In(deleteInstallersNotInList, globalOrTeamID, titleIDs)
@@ -1100,6 +1092,8 @@ ON DUPLICATE KEY UPDATE
installer.UserID,
installer.URL,
strings.Join(installer.PackageIDs, ","),
+ installer.InstallDuringSetup,
+ installer.InstallDuringSetup,
}
upsertQuery := insertNewOrEditedInstaller
if len(existing) > 0 && existing[0].IsPackageModified { // update uploaded_at for updated installer package
diff --git a/server/datastore/mysql/software_installers_test.go b/server/datastore/mysql/software_installers_test.go
index 873e3fea5c46..d33a7941c10e 100644
--- a/server/datastore/mysql/software_installers_test.go
+++ b/server/datastore/mysql/software_installers_test.go
@@ -719,21 +719,23 @@ func testBatchSetSoftwareInstallers(t *testing.T, ds *Datastore) {
})
// add a new installer + ins0 installer
+ // mark ins0 as install_during_setup
ins1 := "installer1"
ins1File := bytes.NewReader([]byte("installer1"))
err = ds.BatchSetSoftwareInstallers(ctx, &team.ID, []*fleet.UploadSoftwareInstallerPayload{
{
- InstallScript: "install",
- InstallerFile: ins0File,
- StorageID: ins0,
- Filename: ins0,
- Title: ins0,
- Source: "apps",
- Version: "1",
- PreInstallQuery: "select 0 from foo;",
- UserID: user1.ID,
- Platform: "darwin",
- URL: "https://example.com",
+ InstallScript: "install",
+ InstallerFile: ins0File,
+ StorageID: ins0,
+ Filename: ins0,
+ Title: ins0,
+ Source: "apps",
+ Version: "1",
+ PreInstallQuery: "select 0 from foo;",
+ UserID: user1.ID,
+ Platform: "darwin",
+ URL: "https://example.com",
+ InstallDuringSetup: ptr.Bool(true),
},
{
InstallScript: "install",
@@ -767,12 +769,6 @@ func testBatchSetSoftwareInstallers(t *testing.T, ds *Datastore) {
{Name: ins1, Source: "apps", Browser: ""},
})
- // mark ins0 as install_during_setup
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- _, err := q.ExecContext(ctx, ` UPDATE software_installers SET install_during_setup = 1 WHERE title_id = ?`, *softwareInstallers[0].TitleID)
- return err
- })
-
// remove ins0 fails due to install_during_setup
err = ds.BatchSetSoftwareInstallers(ctx, &team.ID, []*fleet.UploadSoftwareInstallerPayload{
{
@@ -791,16 +787,72 @@ func testBatchSetSoftwareInstallers(t *testing.T, ds *Datastore) {
require.Error(t, err)
require.ErrorIs(t, err, errDeleteInstallerInstalledDuringSetup)
- // remove everything fails due to ins0 install_during_setup
- err = ds.BatchSetSoftwareInstallers(ctx, &team.ID, []*fleet.UploadSoftwareInstallerPayload{})
- require.Error(t, err)
- require.ErrorIs(t, err, errDeleteInstallerInstalledDuringSetup)
+ // batch-set both installers again, this time with nil install_during_setup for ins0,
+ // will keep it as true.
+ err = ds.BatchSetSoftwareInstallers(ctx, &team.ID, []*fleet.UploadSoftwareInstallerPayload{
+ {
+ InstallScript: "install",
+ InstallerFile: ins0File,
+ StorageID: ins0,
+ Filename: ins0,
+ Title: ins0,
+ Source: "apps",
+ Version: "1",
+ PreInstallQuery: "select 0 from foo;",
+ UserID: user1.ID,
+ Platform: "darwin",
+ URL: "https://example.com",
+ InstallDuringSetup: nil,
+ },
+ {
+ InstallScript: "install",
+ PostInstallScript: "post-install",
+ InstallerFile: ins1File,
+ StorageID: ins1,
+ Filename: ins1,
+ Title: ins1,
+ Source: "apps",
+ Version: "2",
+ PreInstallQuery: "select 1 from bar;",
+ UserID: user1.ID,
+ Platform: "darwin",
+ URL: "https://example2.com",
+ },
+ })
+ require.NoError(t, err)
// mark ins0 as NOT install_during_setup
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- _, err := q.ExecContext(ctx, ` UPDATE software_installers SET install_during_setup = 0 WHERE title_id = ?`, *softwareInstallers[0].TitleID)
- return err
+ err = ds.BatchSetSoftwareInstallers(ctx, &team.ID, []*fleet.UploadSoftwareInstallerPayload{
+ {
+ InstallScript: "install",
+ InstallerFile: ins0File,
+ StorageID: ins0,
+ Filename: ins0,
+ Title: ins0,
+ Source: "apps",
+ Version: "1",
+ PreInstallQuery: "select 0 from foo;",
+ UserID: user1.ID,
+ Platform: "darwin",
+ URL: "https://example.com",
+ InstallDuringSetup: ptr.Bool(false),
+ },
+ {
+ InstallScript: "install",
+ PostInstallScript: "post-install",
+ InstallerFile: ins1File,
+ StorageID: ins1,
+ Filename: ins1,
+ Title: ins1,
+ Source: "apps",
+ Version: "2",
+ PreInstallQuery: "select 1 from bar;",
+ UserID: user1.ID,
+ Platform: "darwin",
+ URL: "https://example2.com",
+ },
})
+ require.NoError(t, err)
// remove ins0
err = ds.BatchSetSoftwareInstallers(ctx, &team.ID, []*fleet.UploadSoftwareInstallerPayload{
diff --git a/server/datastore/mysql/teams_test.go b/server/datastore/mysql/teams_test.go
index 175d49f8eed7..df4b7bc2d782 100644
--- a/server/datastore/mysql/teams_test.go
+++ b/server/datastore/mysql/teams_test.go
@@ -642,6 +642,8 @@ func testTeamsMDMConfig(t *testing.T, ds *Datastore) {
BootstrapPackage: optjson.SetString("bootstrap"),
MacOSSetupAssistant: optjson.SetString("assistant"),
EnableReleaseDeviceManually: optjson.SetBool(false),
+ Script: optjson.String{Set: true},
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.SetSlice([]fleet.MDMProfileSpec{{Path: "foo"}, {Path: "bar"}}),
diff --git a/server/datastore/mysql/vpp.go b/server/datastore/mysql/vpp.go
index 89bd91cade81..03a4c78706c5 100644
--- a/server/datastore/mysql/vpp.go
+++ b/server/datastore/mysql/vpp.go
@@ -169,6 +169,15 @@ func (ds *Datastore) SetTeamVPPApps(ctx context.Context, teamID *uint, appFleets
return ctxerr.Wrap(ctx, err, "SetTeamVPPApps getting list of existing apps")
}
+ // if we're batch-setting apps and replacing the ones installed during setup
+ // in the same go, no need to validate that we don't delete one marked as
+ // install during setup (since we're overwriting those). This is always
+ // called from fleetctl gitops, so it should always be the case anyway.
+ var replacingInstallDuringSetup bool
+ if len(appFleets) == 0 || appFleets[0].InstallDuringSetup != nil {
+ replacingInstallDuringSetup = true
+ }
+
var toAddApps []fleet.VPPAppTeam
var toRemoveApps []fleet.VPPAppID
@@ -181,9 +190,8 @@ func (ds *Datastore) SetTeamVPPApps(ctx context.Context, teamID *uint, appFleets
}
}
if !found {
- // if app is marked as install during setup, prevent deletion.
- // TODO: will need to be reconciled with https://github.com/fleetdm/fleet/issues/22385
- if appTeamInfo.InstallDuringSetup {
+ // if app is marked as install during setup, prevent deletion unless we're replacing those.
+ if !replacingInstallDuringSetup && appTeamInfo.InstallDuringSetup != nil && *appTeamInfo.InstallDuringSetup {
return errDeleteInstallerInstalledDuringSetup
}
toRemoveApps = append(toRemoveApps, existingApp)
@@ -191,7 +199,10 @@ func (ds *Datastore) SetTeamVPPApps(ctx context.Context, teamID *uint, appFleets
}
for _, appFleet := range appFleets {
- if existingFleet, ok := existingApps[appFleet.VPPAppID]; !ok || existingFleet.SelfService != appFleet.SelfService {
+ // upsert it if it does not exist or SelfService or InstallDuringSetup flags are changed
+ if existingFleet, ok := existingApps[appFleet.VPPAppID]; !ok || existingFleet.SelfService != appFleet.SelfService ||
+ appFleet.InstallDuringSetup != nil &&
+ existingFleet.InstallDuringSetup != nil && *appFleet.InstallDuringSetup != *existingFleet.InstallDuringSetup {
toAddApps = append(toAddApps, appFleet)
}
}
@@ -310,11 +321,13 @@ ON DUPLICATE KEY UPDATE
func insertVPPAppTeams(ctx context.Context, tx sqlx.ExtContext, appID fleet.VPPAppTeam, teamID *uint, vppTokenID uint) error {
stmt := `
INSERT INTO vpp_apps_teams
- (adam_id, global_or_team_id, team_id, platform, self_service, vpp_token_id)
+ (adam_id, global_or_team_id, team_id, platform, self_service, vpp_token_id, install_during_setup)
VALUES
- (?, ?, ?, ?, ?, ?)
-ON DUPLICATE KEY UPDATE self_service = VALUES(self_service)
- `
+ (?, ?, ?, ?, ?, ?, COALESCE(?, false))
+ON DUPLICATE KEY UPDATE
+ self_service = VALUES(self_service),
+ install_during_setup = COALESCE(?, install_during_setup)
+`
var globalOrTmID uint
if teamID != nil {
@@ -325,7 +338,7 @@ ON DUPLICATE KEY UPDATE self_service = VALUES(self_service)
}
}
- _, err := tx.ExecContext(ctx, stmt, appID.AdamID, globalOrTmID, teamID, appID.Platform, appID.SelfService, vppTokenID)
+ _, err := tx.ExecContext(ctx, stmt, appID.AdamID, globalOrTmID, teamID, appID.Platform, appID.SelfService, vppTokenID, appID.InstallDuringSetup, appID.InstallDuringSetup)
if IsDuplicate(err) {
err = &existsError{
Identifier: fmt.Sprintf("%s %s self_service: %v", appID.AdamID, appID.Platform, appID.SelfService),
diff --git a/server/datastore/mysql/vpp_test.go b/server/datastore/mysql/vpp_test.go
index 5e355f8d5827..a2f9ca9b5dbf 100644
--- a/server/datastore/mysql/vpp_test.go
+++ b/server/datastore/mysql/vpp_test.go
@@ -511,11 +511,17 @@ func testVPPApps(t *testing.T, ds *Datastore) {
// Check that getting the assigned apps works
appSet, err := ds.GetAssignedVPPApps(ctx, &team.ID)
require.NoError(t, err)
- assert.Equal(t, map[fleet.VPPAppID]fleet.VPPAppTeam{app1.VPPAppID: {VPPAppID: app1.VPPAppID}, app2.VPPAppID: {VPPAppID: app2.VPPAppID}}, appSet)
+ assert.Equal(t, map[fleet.VPPAppID]fleet.VPPAppTeam{
+ app1.VPPAppID: {VPPAppID: app1.VPPAppID, InstallDuringSetup: ptr.Bool(false)},
+ app2.VPPAppID: {VPPAppID: app2.VPPAppID, InstallDuringSetup: ptr.Bool(false)},
+ }, appSet)
appSet, err = ds.GetAssignedVPPApps(ctx, nil)
require.NoError(t, err)
- assert.Equal(t, map[fleet.VPPAppID]fleet.VPPAppTeam{appNoTeam1.VPPAppID: {VPPAppID: appNoTeam1.VPPAppID}, appNoTeam2.VPPAppID: {VPPAppID: appNoTeam2.VPPAppID}}, appSet)
+ assert.Equal(t, map[fleet.VPPAppID]fleet.VPPAppTeam{
+ appNoTeam1.VPPAppID: {VPPAppID: appNoTeam1.VPPAppID, InstallDuringSetup: ptr.Bool(false)},
+ appNoTeam2.VPPAppID: {VPPAppID: appNoTeam2.VPPAppID, InstallDuringSetup: ptr.Bool(false)},
+ }, appSet)
var appTitles []fleet.SoftwareTitle
err = sqlx.SelectContext(ctx, ds.reader(ctx), &appTitles, `SELECT name, bundle_identifier FROM software_titles WHERE bundle_identifier IN (?,?) ORDER BY bundle_identifier`, app1.BundleIdentifier, app2.BundleIdentifier)
@@ -560,29 +566,24 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
require.Len(t, assigned, 0)
// Assign 2 apps
+ // make app1 install_during_setup for that team
err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{
- {VPPAppID: app1.VPPAppID},
+ {VPPAppID: app1.VPPAppID, InstallDuringSetup: ptr.Bool(true)},
{VPPAppID: app2.VPPAppID, SelfService: true},
})
require.NoError(t, err)
- // make app1 install_during_setup for that team
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- _, err := q.ExecContext(ctx, `UPDATE vpp_apps_teams SET install_during_setup = 1 WHERE global_or_team_id = ? AND adam_id = ?`, team.ID, app1.AdamID)
- return err
- })
-
assigned, err = ds.GetAssignedVPPApps(ctx, &team.ID)
require.NoError(t, err)
require.Len(t, assigned, 2)
assert.Contains(t, assigned, app1.VPPAppID)
assert.Contains(t, assigned, app2.VPPAppID)
assert.True(t, assigned[app2.VPPAppID].SelfService)
- assert.True(t, assigned[app1.VPPAppID].InstallDuringSetup)
+ assert.True(t, *assigned[app1.VPPAppID].InstallDuringSetup)
// Assign an additional app
err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{
- {VPPAppID: app1.VPPAppID},
+ {VPPAppID: app1.VPPAppID, InstallDuringSetup: ptr.Bool(true)},
{VPPAppID: app2.VPPAppID},
{VPPAppID: app3.VPPAppID},
})
@@ -595,11 +596,11 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
require.Contains(t, assigned, app2.VPPAppID)
require.Contains(t, assigned, app3.VPPAppID)
assert.False(t, assigned[app2.VPPAppID].SelfService)
- assert.True(t, assigned[app1.VPPAppID].InstallDuringSetup)
+ assert.True(t, *assigned[app1.VPPAppID].InstallDuringSetup)
// Swap one app out for another
err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{
- {VPPAppID: app1.VPPAppID},
+ {VPPAppID: app1.VPPAppID, InstallDuringSetup: ptr.Bool(true)},
{VPPAppID: app2.VPPAppID, SelfService: true},
{VPPAppID: app4.VPPAppID},
})
@@ -612,7 +613,7 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
require.Contains(t, assigned, app2.VPPAppID)
require.Contains(t, assigned, app4.VPPAppID)
assert.True(t, assigned[app2.VPPAppID].SelfService)
- assert.True(t, assigned[app1.VPPAppID].InstallDuringSetup)
+ assert.True(t, *assigned[app1.VPPAppID].InstallDuringSetup)
// Remove app1 fails because it is installed during setup
err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{
@@ -622,18 +623,22 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
require.Error(t, err)
require.ErrorIs(t, err, errDeleteInstallerInstalledDuringSetup)
- // Remove all apps fails because app1 is installed during setup
- err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{})
- require.Error(t, err)
- require.ErrorIs(t, err, errDeleteInstallerInstalledDuringSetup)
-
// make app1 NOT install_during_setup for that team
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- _, err := q.ExecContext(ctx, `UPDATE vpp_apps_teams SET install_during_setup = 0 WHERE global_or_team_id = ? AND adam_id = ?`, team.ID, app1.AdamID)
- return err
+ err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{
+ {VPPAppID: app1.VPPAppID, InstallDuringSetup: ptr.Bool(false)},
+ {VPPAppID: app2.VPPAppID, SelfService: true},
+ {VPPAppID: app4.VPPAppID},
})
+ require.NoError(t, err)
+
+ // Remove app1 now works
+ err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{
+ {VPPAppID: app2.VPPAppID, SelfService: true},
+ {VPPAppID: app4.VPPAppID},
+ })
+ require.NoError(t, err)
- // Remove all apps now works
+ // Remove all apps
err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{})
require.NoError(t, err)
diff --git a/server/fleet/app.go b/server/fleet/app.go
index f0810e352df6..483dd5fe796a 100644
--- a/server/fleet/app.go
+++ b/server/fleet/app.go
@@ -420,10 +420,19 @@ func (s *MacOSSettings) FromMap(m map[string]interface{}) (map[string]bool, erro
// MacOSSetup contains settings related to the setup of DEP enrolled devices.
type MacOSSetup struct {
- BootstrapPackage optjson.String `json:"bootstrap_package"`
- EnableEndUserAuthentication bool `json:"enable_end_user_authentication"`
- MacOSSetupAssistant optjson.String `json:"macos_setup_assistant"`
- EnableReleaseDeviceManually optjson.Bool `json:"enable_release_device_manually"`
+ BootstrapPackage optjson.String `json:"bootstrap_package"`
+ EnableEndUserAuthentication bool `json:"enable_end_user_authentication"`
+ MacOSSetupAssistant optjson.String `json:"macos_setup_assistant"`
+ EnableReleaseDeviceManually optjson.Bool `json:"enable_release_device_manually"`
+ Script optjson.String `json:"script"`
+ Software optjson.Slice[*MacOSSetupSoftware] `json:"software"`
+}
+
+// MacOSSetupSoftware represents a VPP app or a software package to install
+// during the setup experience of a macOS device.
+type MacOSSetupSoftware struct {
+ AppStoreID string `json:"app_store_id"`
+ PackagePath string `json:"package_path"`
}
// MacOSMigration contains settings related to the MDM migration work flow.
@@ -670,6 +679,15 @@ func (c *AppConfig) Copy() *AppConfig {
clone.MDM.VolumePurchasingProgram = optjson.SetSlice(vpp)
}
+ if c.MDM.MacOSSetup.Software.Set {
+ sw := make([]*MacOSSetupSoftware, len(c.MDM.MacOSSetup.Software.Value))
+ for i, s := range c.MDM.MacOSSetup.Software.Value {
+ s := *s
+ sw[i] = &s
+ }
+ clone.MDM.MacOSSetup.Software = optjson.SetSlice(sw)
+ }
+
return &clone
}
diff --git a/server/fleet/scripts.go b/server/fleet/scripts.go
index 1dcbb343e9bc..28bba6ad8cf7 100644
--- a/server/fleet/scripts.go
+++ b/server/fleet/scripts.go
@@ -373,14 +373,15 @@ type ScriptPayload struct {
}
type SoftwareInstallerPayload struct {
- URL string `json:"url"`
- PreInstallQuery string `json:"pre_install_query"`
- InstallScript string `json:"install_script"`
- UninstallScript string `json:"uninstall_script"`
- PostInstallScript string `json:"post_install_script"`
- SelfService bool `json:"self_service"`
- FleetMaintained bool `json:"-"`
- Filename string `json:"-"`
+ URL string `json:"url"`
+ PreInstallQuery string `json:"pre_install_query"`
+ InstallScript string `json:"install_script"`
+ UninstallScript string `json:"uninstall_script"`
+ PostInstallScript string `json:"post_install_script"`
+ SelfService bool `json:"self_service"`
+ FleetMaintained bool `json:"-"`
+ Filename string `json:"-"`
+ InstallDuringSetup *bool `json:"install_during_setup"` // if nil, do not change saved value, otherwise set it
}
type HostLockWipeStatus struct {
diff --git a/server/fleet/software.go b/server/fleet/software.go
index 1b9eecc813c7..0f9eeab4f21f 100644
--- a/server/fleet/software.go
+++ b/server/fleet/software.go
@@ -427,12 +427,14 @@ func SoftwareFromOsqueryRow(name, version, source, vendor, installedPath, releas
}
type VPPBatchPayload struct {
- AppStoreID string `json:"app_store_id"`
- SelfService bool `json:"self_service"`
+ AppStoreID string `json:"app_store_id"`
+ SelfService bool `json:"self_service"`
+ InstallDuringSetup *bool `json:"install_during_setup"` // keep saved value if nil, otherwise set as indicated
}
type VPPBatchPayloadWithPlatform struct {
- AppStoreID string `json:"app_store_id"`
- SelfService bool `json:"self_service"`
- Platform AppleDevicePlatform `json:"platform"`
+ AppStoreID string `json:"app_store_id"`
+ SelfService bool `json:"self_service"`
+ Platform AppleDevicePlatform `json:"platform"`
+ InstallDuringSetup *bool `json:"install_during_setup"` // keep saved value if nil, otherwise set as indicated
}
diff --git a/server/fleet/software_installer.go b/server/fleet/software_installer.go
index 944ec125de58..d715e1ddc5cf 100644
--- a/server/fleet/software_installer.go
+++ b/server/fleet/software_installer.go
@@ -308,25 +308,26 @@ func (s *HostSoftwareInstallerResultAuthz) AuthzType() string {
}
type UploadSoftwareInstallerPayload struct {
- TeamID *uint
- InstallScript string
- PreInstallQuery string
- PostInstallScript string
- InstallerFile io.ReadSeeker // TODO: maybe pull this out of the payload and only pass it to methods that need it (e.g., won't be needed when storing metadata in the database)
- StorageID string
- Filename string
- Title string
- Version string
- Source string
- Platform string
- BundleIdentifier string
- SelfService bool
- UserID uint
- URL string
- FleetLibraryAppID *uint
- PackageIDs []string
- UninstallScript string
- Extension string
+ TeamID *uint
+ InstallScript string
+ PreInstallQuery string
+ PostInstallScript string
+ InstallerFile io.ReadSeeker // TODO: maybe pull this out of the payload and only pass it to methods that need it (e.g., won't be needed when storing metadata in the database)
+ StorageID string
+ Filename string
+ Title string
+ Version string
+ Source string
+ Platform string
+ BundleIdentifier string
+ SelfService bool
+ UserID uint
+ URL string
+ FleetLibraryAppID *uint
+ PackageIDs []string
+ UninstallScript string
+ Extension string
+ InstallDuringSetup *bool // keep saved value if nil, otherwise set as indicated
}
type UpdateSoftwareInstallerPayload struct {
@@ -438,6 +439,16 @@ type SoftwarePackageSpec struct {
InstallScript TeamSpecSoftwareAsset `json:"install_script"`
PostInstallScript TeamSpecSoftwareAsset `json:"post_install_script"`
UninstallScript TeamSpecSoftwareAsset `json:"uninstall_script"`
+
+ // ReferencedYamlPath is the resolved path of the file used to fill the
+ // software package. Only present after parsing a GitOps file on the fleetctl
+ // side of processing. This is required to match a macos_setup.software to
+ // its corresponding software package, as we do this matching by yaml path.
+ //
+ // It must be JSON-marshaled because it gets set during gitops file processing,
+ // which is then re-marshaled to JSON from this struct and later re-unmarshaled
+ // during ApplyGroup...
+ ReferencedYamlPath string `json:"referenced_yaml_path"`
}
type SoftwareSpec struct {
diff --git a/server/fleet/teams.go b/server/fleet/teams.go
index 68cde3072dcc..4ee8b53aad65 100644
--- a/server/fleet/teams.go
+++ b/server/fleet/teams.go
@@ -239,6 +239,14 @@ func (t *TeamMDM) Copy() *TeamMDM {
}
clone.WindowsSettings.CustomSettings = optjson.SetSlice(windowsSettings)
}
+ if t.MacOSSetup.Software.Set {
+ sw := make([]*MacOSSetupSoftware, len(t.MacOSSetup.Software.Value))
+ for i, s := range t.MacOSSetup.Software.Value {
+ s := *s
+ sw[i] = &s
+ }
+ clone.MacOSSetup.Software = optjson.SetSlice(sw)
+ }
return &clone
}
@@ -518,5 +526,7 @@ func TeamSpecFromTeam(t *Team) (*TeamSpec, error) {
HostExpirySettings: &t.Config.HostExpirySettings,
WebhookSettings: webhookSettings,
Integrations: integrations,
+ Scripts: t.Config.Scripts,
+ Software: t.Config.Software,
}, nil
}
diff --git a/server/fleet/vpp.go b/server/fleet/vpp.go
index c746844a4dbc..a5ecc83ca6ef 100644
--- a/server/fleet/vpp.go
+++ b/server/fleet/vpp.go
@@ -16,8 +16,13 @@ type VPPAppID struct {
type VPPAppTeam struct {
VPPAppID
- SelfService bool `db:"self_service" json:"self_service"`
- InstallDuringSetup bool `db:"install_during_setup" json:"-"`
+ SelfService bool `db:"self_service" json:"self_service"`
+
+ // InstallDuringSetup is either the stored value of that flag for the VPP app
+ // or the value to set to that VPP app when batch-setting it. When used to
+ // set the value, if nil it will keep the currently saved value (or default
+ // to false), while if not nil, it will update the flag's value in the DB.
+ InstallDuringSetup *bool `db:"install_during_setup" json:"-"`
}
// VPPApp represents a VPP (Volume Purchase Program) application,
diff --git a/server/service/appconfig_test.go b/server/service/appconfig_test.go
index 44d81c756671..87081047c61e 100644
--- a/server/service/appconfig_test.go
+++ b/server/service/appconfig_test.go
@@ -908,8 +908,14 @@ func TestMDMAppleConfig(t *testing.T) {
name: "nochange",
licenseTier: "free",
expectedMDM: fleet.MDM{
- AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
- MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
+ AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
+ MacOSSetup: fleet.MacOSSetup{
+ BootstrapPackage: optjson.String{Set: true},
+ MacOSSetupAssistant: optjson.String{Set: true},
+ EnableReleaseDeviceManually: optjson.SetBool(false),
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
+ Script: optjson.String{Set: true},
+ },
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
@@ -943,12 +949,18 @@ func TestMDMAppleConfig(t *testing.T) {
expectedMDM: fleet.MDM{
AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
DeprecatedAppleBMDefaultTeam: "foobar",
- MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
- MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
- IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
- IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
- VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}},
- WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
+ MacOSSetup: fleet.MacOSSetup{
+ BootstrapPackage: optjson.String{Set: true},
+ MacOSSetupAssistant: optjson.String{Set: true},
+ EnableReleaseDeviceManually: optjson.SetBool(false),
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
+ Script: optjson.String{Set: true},
+ },
+ MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
+ IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
+ IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
+ VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}},
+ WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
},
@@ -962,12 +974,18 @@ func TestMDMAppleConfig(t *testing.T) {
expectedMDM: fleet.MDM{
AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
DeprecatedAppleBMDefaultTeam: "foobar",
- MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
- MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
- IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
- IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
- VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}},
- WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
+ MacOSSetup: fleet.MacOSSetup{
+ BootstrapPackage: optjson.String{Set: true},
+ MacOSSetupAssistant: optjson.String{Set: true},
+ EnableReleaseDeviceManually: optjson.SetBool(false),
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
+ Script: optjson.String{Set: true},
+ },
+ MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
+ IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
+ IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
+ VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}},
+ WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
},
@@ -985,9 +1003,15 @@ func TestMDMAppleConfig(t *testing.T) {
newMDM: fleet.MDM{EndUserAuthentication: fleet.MDMEndUserAuthentication{SSOProviderSettings: fleet.SSOProviderSettings{EntityID: "foo"}}},
oldMDM: fleet.MDM{EndUserAuthentication: fleet.MDMEndUserAuthentication{SSOProviderSettings: fleet.SSOProviderSettings{EntityID: "foo"}}},
expectedMDM: fleet.MDM{
- AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
- EndUserAuthentication: fleet.MDMEndUserAuthentication{SSOProviderSettings: fleet.SSOProviderSettings{EntityID: "foo"}},
- MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
+ AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
+ EndUserAuthentication: fleet.MDMEndUserAuthentication{SSOProviderSettings: fleet.SSOProviderSettings{EntityID: "foo"}},
+ MacOSSetup: fleet.MacOSSetup{
+ BootstrapPackage: optjson.String{Set: true},
+ MacOSSetupAssistant: optjson.String{Set: true},
+ EnableReleaseDeviceManually: optjson.SetBool(false),
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
+ Script: optjson.String{Set: true},
+ },
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
@@ -1015,7 +1039,13 @@ func TestMDMAppleConfig(t *testing.T) {
MetadataURL: "http://isser.metadata.com",
IDPName: "onelogin",
}},
- MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
+ MacOSSetup: fleet.MacOSSetup{
+ BootstrapPackage: optjson.String{Set: true},
+ MacOSSetupAssistant: optjson.String{Set: true},
+ EnableReleaseDeviceManually: optjson.SetBool(false),
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
+ Script: optjson.String{Set: true},
+ },
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
@@ -1075,9 +1105,15 @@ func TestMDMAppleConfig(t *testing.T) {
EnableDiskEncryption: optjson.SetBool(false),
},
expectedMDM: fleet.MDM{
- AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
- EnableDiskEncryption: optjson.Bool{Set: true, Valid: true, Value: false},
- MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
+ AppleBusinessManager: optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{Set: true, Value: []fleet.MDMAppleABMAssignmentInfo{}},
+ EnableDiskEncryption: optjson.Bool{Set: true, Valid: true, Value: false},
+ MacOSSetup: fleet.MacOSSetup{
+ BootstrapPackage: optjson.String{Set: true},
+ MacOSSetupAssistant: optjson.String{Set: true},
+ EnableReleaseDeviceManually: optjson.SetBool(false),
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
+ Script: optjson.String{Set: true},
+ },
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
diff --git a/server/service/client.go b/server/service/client.go
index 247ba1b93546..52223b60e70e 100644
--- a/server/service/client.go
+++ b/server/service/client.go
@@ -389,9 +389,32 @@ func getProfilesContents(baseDir string, macProfiles []fleet.MDMProfileSpec, win
return result, nil
}
+// fileContent is used to store the name of a file and its content.
+type fileContent struct {
+ Filename string
+ Content []byte
+}
+
+// TODO: as confirmed by Noah and Marko on Slack:
+//
+// > from Noah: "We want to support existing features w/ fleetctl apply for
+// > backwards compatibility GitOps but we don’t need to add new features."
+//
+// We should deprecate ApplyGroup and use it only for `fleetctl apply` (and
+// its current minimal use in `preview`), and have a distinct implementation
+// that is `gitops`-only, because both uses have subtle differences in
+// behaviour that make it hard to reuse a single implementation (e.g. a missing
+// key in gitops means "remove what is absent" while in apply it means "leave
+// as-is").
+//
+// For now I'm just passing a "gitops" bool for a quick fix, but we should
+// properly plan that separation and refactor so that gitops can be
+// significantly cleaned up and simplified going forward.
+
// ApplyGroup applies the given spec group to Fleet.
func (c *Client) ApplyGroup(
ctx context.Context,
+ viaGitOps bool,
specs *spec.Group,
baseDir string,
logf func(format string, args ...interface{}),
@@ -554,6 +577,15 @@ func (c *Client) ApplyGroup(
tmMacSetup := extractTmSpecsMacOSSetup(specs.Teams)
tmBootstrapPackages := make(map[string]*fleet.MDMAppleBootstrapPackage, len(tmMacSetup))
tmMacSetupAssistants := make(map[string][]byte, len(tmMacSetup))
+
+ // those are gitops-only features
+ tmMacSetupScript := make(map[string]fileContent, len(tmMacSetup))
+ tmMacSetupSoftware := make(map[string][]*fleet.MacOSSetupSoftware, len(tmMacSetup))
+ // this is a set of software packages or VPP apps that are configured as
+ // install_during_setup, by team. This is a gitops-only setting, so it will
+ // only be filled when called via this command.
+ tmSoftwareMacOSSetup := make(map[string]map[fleet.MacOSSetupSoftware]struct{}, len(tmMacSetup))
+
for k, setup := range tmMacSetup {
if setup.BootstrapPackage.Value != "" {
bp, err := c.ValidateBootstrapPackageFromURL(setup.BootstrapPackage.Value)
@@ -569,6 +601,21 @@ func (c *Client) ApplyGroup(
}
tmMacSetupAssistants[k] = b
}
+ if setup.Script.Value != "" {
+ b, err := c.validateMacOSSetupScript(resolveApplyRelativePath(baseDir, setup.Script.Value))
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("applying teams: %w", err)
+ }
+ tmMacSetupScript[k] = fileContent{Filename: filepath.Base(setup.Script.Value), Content: b}
+ }
+ if viaGitOps {
+ m, err := extractTeamOrNoTeamMacOSSetupSoftware(baseDir, setup.Software.Value)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ tmSoftwareMacOSSetup[k] = m
+ tmMacSetupSoftware[k] = setup.Software.Value
+ }
}
tmScripts := extractTmSpecsScripts(specs.Teams)
@@ -590,24 +637,59 @@ func (c *Client) ApplyGroup(
tmSoftwarePackages := extractTmSpecsSoftwarePackages(specs.Teams)
tmSoftwarePackagesPayloads := make(map[string][]fleet.SoftwareInstallerPayload, len(tmSoftwarePackages))
+ tmSoftwarePackageByPath := make(map[string]map[string]fleet.SoftwarePackageSpec, len(tmSoftwarePackages))
for tmName, software := range tmSoftwarePackages {
- softwarePayloads, err := buildSoftwarePackagesPayload(baseDir, software)
+ installDuringSetupKeys := tmSoftwareMacOSSetup[tmName]
+ softwarePayloads, err := buildSoftwarePackagesPayload(baseDir, software, installDuringSetupKeys)
if err != nil {
return nil, nil, nil, fmt.Errorf("applying software installers for team %q: %w", tmName, err)
}
tmSoftwarePackagesPayloads[tmName] = softwarePayloads
+ for _, swSpec := range software {
+ if swSpec.ReferencedYamlPath != "" {
+ // can be referenced by macos_setup.software.package_path
+ if tmSoftwarePackageByPath[tmName] == nil {
+ tmSoftwarePackageByPath[tmName] = make(map[string]fleet.SoftwarePackageSpec, len(software))
+ }
+ tmSoftwarePackageByPath[tmName][swSpec.ReferencedYamlPath] = swSpec
+ }
+ }
}
tmSoftwareApps := extractTmSpecsSoftwareApps(specs.Teams)
tmSoftwareAppsPayloads := make(map[string][]fleet.VPPBatchPayload)
+ tmSoftwareAppsByAppID := make(map[string]map[string]fleet.TeamSpecAppStoreApp, len(tmSoftwareApps))
for tmName, apps := range tmSoftwareApps {
+ installDuringSetupKeys := tmSoftwareMacOSSetup[tmName]
appPayloads := make([]fleet.VPPBatchPayload, 0, len(apps))
for _, app := range apps {
- appPayloads = append(appPayloads, fleet.VPPBatchPayload{AppStoreID: app.AppStoreID, SelfService: app.SelfService})
+ var installDuringSetup *bool
+ if installDuringSetupKeys != nil {
+ _, ok := installDuringSetupKeys[fleet.MacOSSetupSoftware{AppStoreID: app.AppStoreID}]
+ installDuringSetup = &ok
+ }
+ appPayloads = append(appPayloads, fleet.VPPBatchPayload{
+ AppStoreID: app.AppStoreID,
+ SelfService: app.SelfService,
+ InstallDuringSetup: installDuringSetup,
+ })
+ // can be referenced by macos_setup.software.app_store_id
+ if tmSoftwareAppsByAppID[tmName] == nil {
+ tmSoftwareAppsByAppID[tmName] = make(map[string]fleet.TeamSpecAppStoreApp, len(apps))
+ }
+ tmSoftwareAppsByAppID[tmName][app.AppStoreID] = app
}
tmSoftwareAppsPayloads[tmName] = appPayloads
}
+ // if macos_setup.software has some values, they must exist in the software
+ // packages or vpp apps.
+ for tmName, setupSw := range tmMacSetupSoftware {
+ if err := validateTeamOrNoTeamMacOSSetupSoftware(tmName, setupSw, tmSoftwarePackageByPath[tmName], tmSoftwareAppsByAppID[tmName]); err != nil {
+ return nil, nil, nil, err
+ }
+ }
+
// Next, apply the teams specs before saving the profiles, so that any
// non-existing team gets created.
var err error
@@ -675,6 +757,19 @@ func (c *Client) ApplyGroup(
}
}
}
+ if viaGitOps && !opts.DryRun {
+ for tmName, tmID := range teamIDsByName {
+ if fc, ok := tmMacSetupScript[tmName]; ok {
+ if err := c.uploadMacOSSetupScript(fc.Filename, fc.Content, &tmID); err != nil {
+ return nil, nil, nil, fmt.Errorf("uploading setup experience script for team %q: %w", tmName, err)
+ }
+ } else {
+ if err := c.deleteMacOSSetupScript(&tmID); err != nil {
+ return nil, nil, nil, fmt.Errorf("deleting setup experience script for team %q: %w", tmName, err)
+ }
+ }
+ }
+ }
if len(tmScriptsPayloads) > 0 {
for tmName, scripts := range tmScriptsPayloads {
// For non-dry run, currentTeamName and tmName are the same
@@ -702,6 +797,7 @@ func (c *Client) ApplyGroup(
for tmName, apps := range tmSoftwareAppsPayloads {
// For non-dry run, currentTeamName and tmName are the same
currentTeamName := getTeamName(tmName)
+ logfn("[+] applying %d app store apps for team %s\n", len(apps), tmName)
if err := c.ApplyTeamAppStoreAppsAssociation(currentTeamName, apps, opts.ApplySpecOptions); err != nil {
return nil, nil, nil, fmt.Errorf("applying app store apps for team: %q: %w", tmName, err)
}
@@ -752,7 +848,45 @@ func (c *Client) ApplyGroup(
return teamIDsByName, teamsSoftwareInstallers, teamsScripts, nil
}
-func buildSoftwarePackagesPayload(baseDir string, specs []fleet.SoftwarePackageSpec) ([]fleet.SoftwareInstallerPayload, error) {
+func extractTeamOrNoTeamMacOSSetupSoftware(baseDir string, software []*fleet.MacOSSetupSoftware) (map[fleet.MacOSSetupSoftware]struct{}, error) {
+ m := make(map[fleet.MacOSSetupSoftware]struct{}, len(software))
+ for _, sw := range software {
+ if sw.AppStoreID != "" && sw.PackagePath != "" {
+ return nil, errors.New("applying teams: only one of app_store_id or package_path can be set")
+ }
+ if sw.PackagePath != "" {
+ sw.PackagePath = resolveApplyRelativePath(baseDir, sw.PackagePath)
+ }
+ m[*sw] = struct{}{}
+ }
+ return m, nil
+}
+
+func validateTeamOrNoTeamMacOSSetupSoftware(teamName string, macOSSetupSoftware []*fleet.MacOSSetupSoftware, packagesByPath map[string]fleet.SoftwarePackageSpec, vppAppsByAppID map[string]fleet.TeamSpecAppStoreApp) error {
+ // if macos_setup.software has some values, they must exist in the software
+ // packages or vpp apps.
+ for _, ssw := range macOSSetupSoftware {
+ var valid bool
+ if ssw.AppStoreID != "" {
+ // check that it exists in the team's Apps
+ _, valid = vppAppsByAppID[ssw.AppStoreID]
+ } else if ssw.PackagePath != "" {
+ // check that it exists in the team's Software installers (PackagePath is
+ // already resolved to abs dir)
+ _, valid = packagesByPath[ssw.PackagePath]
+ }
+ if !valid {
+ label := ssw.AppStoreID
+ if label == "" {
+ label = ssw.PackagePath
+ }
+ return fmt.Errorf("applying macOS setup experience software for team %q: software %q does not exist for that team", teamName, label)
+ }
+ }
+ return nil
+}
+
+func buildSoftwarePackagesPayload(baseDir string, specs []fleet.SoftwarePackageSpec, installDuringSetupKeys map[fleet.MacOSSetupSoftware]struct{}) ([]fleet.SoftwareInstallerPayload, error) {
softwarePayloads := make([]fleet.SoftwareInstallerPayload, len(specs))
for i, si := range specs {
var qc string
@@ -838,15 +972,20 @@ func buildSoftwarePackagesPayload(baseDir string, specs []fleet.SoftwarePackageS
}
}
+ var installDuringSetup *bool
+ if installDuringSetupKeys != nil {
+ _, ok := installDuringSetupKeys[fleet.MacOSSetupSoftware{PackagePath: si.ReferencedYamlPath}]
+ installDuringSetup = &ok
+ }
softwarePayloads[i] = fleet.SoftwareInstallerPayload{
- URL: si.URL,
- SelfService: si.SelfService,
- PreInstallQuery: qc,
- InstallScript: string(ic),
- PostInstallScript: string(pc),
- UninstallScript: string(us),
+ URL: si.URL,
+ SelfService: si.SelfService,
+ PreInstallQuery: qc,
+ InstallScript: string(ic),
+ PostInstallScript: string(pc),
+ UninstallScript: string(us),
+ InstallDuringSetup: installDuringSetup,
}
-
}
return softwarePayloads, nil
@@ -1478,7 +1617,7 @@ func (c *Client) DoGitOps(
}
// Apply org settings, scripts, enroll secrets, team entities (software, scripts, etc.), and controls.
- teamIDsByName, teamsSoftwareInstallers, teamsScripts, err := c.ApplyGroup(ctx, &group, baseDir, logf, appConfig, fleet.ApplyClientSpecOptions{
+ teamIDsByName, teamsSoftwareInstallers, teamsScripts, err := c.ApplyGroup(ctx, true, &group, baseDir, logf, appConfig, fleet.ApplyClientSpecOptions{
ApplySpecOptions: fleet.ApplySpecOptions{
DryRun: dryRun,
},
@@ -1535,30 +1674,89 @@ func (c *Client) DoGitOps(
return teamAssumptions, nil
}
-func (c *Client) doGitOpsNoTeamSoftware(config *spec.GitOps, baseDir string, appconfig *fleet.EnrichedAppConfig, logFn func(format string, args ...interface{}), dryRun bool) ([]fleet.SoftwarePackageResponse, error) {
+func (c *Client) doGitOpsNoTeamSoftware(
+ config *spec.GitOps,
+ baseDir string,
+ appconfig *fleet.EnrichedAppConfig,
+ logFn func(format string, args ...interface{}),
+ dryRun bool,
+) ([]fleet.SoftwarePackageResponse, error) {
+ if !config.IsNoTeam() || appconfig == nil || !appconfig.License.IsPremium() {
+ return nil, nil
+ }
+
var softwareInstallers []fleet.SoftwarePackageResponse
- if config.IsNoTeam() && appconfig != nil && appconfig.License.IsPremium() {
- packages := make([]fleet.SoftwarePackageSpec, 0, len(config.Software.Packages))
- for _, software := range config.Software.Packages {
- if software != nil {
- packages = append(packages, *software)
+
+ packages := make([]fleet.SoftwarePackageSpec, 0, len(config.Software.Packages))
+ packagesByPath := make(map[string]fleet.SoftwarePackageSpec, len(config.Software.Packages))
+ for _, software := range config.Software.Packages {
+ if software != nil {
+ packages = append(packages, *software)
+ if software.ReferencedYamlPath != "" {
+ // can be referenced by macos_setup.software
+ packagesByPath[software.ReferencedYamlPath] = *software
}
}
- payload, err := buildSoftwarePackagesPayload(baseDir, packages)
- if err != nil {
- return nil, fmt.Errorf("applying software installers: %w", err)
- }
- logFn("[+] applying %d software packages for 'No team'\n", len(payload))
- softwareInstallers, err = c.ApplyNoTeamSoftwareInstallers(payload, fleet.ApplySpecOptions{DryRun: dryRun})
+ }
+
+ // marshaling dance to get the macos_setup data - config.Controls.MacOSSetup
+ // is of type any and contains a generic map[string]any. By
+ // marshal-unmarshaling it into a properly typed struct, we avoid having to
+ // do a bunch of error-prone and unmaintainable type-assertions to walk down
+ // the untyped map.
+ b, err := json.Marshal(config.Controls.MacOSSetup)
+ if err != nil {
+ return nil, fmt.Errorf("applying software installers: json-encode controls.macos_setup: %w", err)
+ }
+ var macOSSetup fleet.MacOSSetup
+ if err := json.Unmarshal(b, &macOSSetup); err != nil {
+ return nil, fmt.Errorf("applying software installers: json-decode controls.macos_setup: %w", err)
+ }
+
+ // load the no-team macos_setup.script if any
+ var macosSetupScript *fileContent
+ if macOSSetup.Script.Value != "" {
+ b, err := c.validateMacOSSetupScript(resolveApplyRelativePath(baseDir, macOSSetup.Script.Value))
if err != nil {
- return nil, fmt.Errorf("applying software installers: %w", err)
+ return nil, fmt.Errorf("applying no team macos_setup.script: %w", err)
}
+ macosSetupScript = &fileContent{Filename: filepath.Base(macOSSetup.Script.Value), Content: b}
+ }
- if dryRun {
- logFn("[+] would've applied 'No Team' software packages\n")
- } else {
- logFn("[+] applied 'No Team' software packages\n")
+ noTeamSoftwareMacOSSetup, err := extractTeamOrNoTeamMacOSSetupSoftware(baseDir, macOSSetup.Software.Value)
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO: note that VPP apps are not validated nor taken into account at the moment,
+ // tracked with issue https://github.com/fleetdm/fleet/issues/22970
+ if err := validateTeamOrNoTeamMacOSSetupSoftware(*config.TeamName, macOSSetup.Software.Value, packagesByPath, nil); err != nil {
+ return nil, err
+ }
+ payload, err := buildSoftwarePackagesPayload(baseDir, packages, noTeamSoftwareMacOSSetup)
+ if err != nil {
+ return nil, fmt.Errorf("applying software installers: %w", err)
+ }
+
+ if macosSetupScript != nil {
+ logFn("[+] applying macos setup experience script for 'No team'\n")
+ if err := c.uploadMacOSSetupScript(macosSetupScript.Filename, macosSetupScript.Content, nil); err != nil {
+ return nil, fmt.Errorf("uploading setup experience script for No team: %w", err)
}
+ } else if err := c.deleteMacOSSetupScript(nil); err != nil {
+ return nil, fmt.Errorf("deleting setup experience script for No team: %w", err)
+ }
+
+ logFn("[+] applying %d software packages for 'No team'\n", len(payload))
+ softwareInstallers, err = c.ApplyNoTeamSoftwareInstallers(payload, fleet.ApplySpecOptions{DryRun: dryRun})
+ if err != nil {
+ return nil, fmt.Errorf("applying software installers: %w", err)
+ }
+
+ if dryRun {
+ logFn("[+] would've applied 'No Team' software packages\n")
+ } else {
+ logFn("[+] applied 'No Team' software packages\n")
}
return softwareInstallers, nil
}
diff --git a/server/service/client_scripts.go b/server/service/client_scripts.go
index 7a47c162e908..9761fe24b214 100644
--- a/server/service/client_scripts.go
+++ b/server/service/client_scripts.go
@@ -1,11 +1,15 @@
package service
import (
+ "bytes"
+ "context"
"encoding/json"
"errors"
"fmt"
"io"
+ "mime/multipart"
"net/http"
+ "os"
"strings"
"time"
@@ -139,3 +143,78 @@ func (c *Client) ApplyNoTeamScripts(scripts []fleet.ScriptPayload, opts fleet.Ap
return resp.Scripts, err
}
+
+func (c *Client) validateMacOSSetupScript(fileName string) ([]byte, error) {
+ if err := c.CheckAppleMDMEnabled(); err != nil {
+ return nil, err
+ }
+
+ b, err := os.ReadFile(fileName)
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+func (c *Client) deleteMacOSSetupScript(teamID *uint) error {
+ var query string
+ if teamID != nil {
+ query = fmt.Sprintf("team_id=%d", *teamID)
+ }
+
+ verb, path := "DELETE", "/api/latest/fleet/setup_experience/script"
+ var delResp deleteSetupExperienceScriptResponse
+ return c.authenticatedRequestWithQuery(nil, verb, path, &delResp, query)
+}
+
+func (c *Client) uploadMacOSSetupScript(filename string, data []byte, teamID *uint) error {
+ // there is no "replace setup experience script" endpoint, and none was
+ // planned, so to avoid delaying the feature I'm doing DELETE then SET, but
+ // that's not ideal (will always re-create the script when apply/gitops is
+ // run with the same yaml). Note though that we also redo software installers
+ // downloads on each run, so the churn of this one is minor in comparison.
+ if err := c.deleteMacOSSetupScript(teamID); err != nil {
+ return err
+ }
+
+ verb, path := "POST", "/api/latest/fleet/setup_experience/script"
+
+ var b bytes.Buffer
+ w := multipart.NewWriter(&b)
+
+ fw, err := w.CreateFormFile("script", filename)
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(fw, bytes.NewBuffer(data)); err != nil {
+ return err
+ }
+
+ // add the team_id field
+ if teamID != nil {
+ if err := w.WriteField("team_id", fmt.Sprint(*teamID)); err != nil {
+ return err
+ }
+ }
+ w.Close()
+
+ response, err := c.doContextWithBodyAndHeaders(context.Background(), verb, path, "",
+ b.Bytes(),
+ map[string]string{
+ "Content-Type": w.FormDataContentType(),
+ "Accept": "application/json",
+ "Authorization": fmt.Sprintf("Bearer %s", c.token),
+ },
+ )
+ if err != nil {
+ return fmt.Errorf("do multipart request: %w", err)
+ }
+ defer response.Body.Close()
+
+ var resp setSetupExperienceScriptResponse
+ if err := c.parseResponse(verb, path, response, &resp); err != nil {
+ return fmt.Errorf("parse response: %w", err)
+ }
+
+ return nil
+}
diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go
index d64896ab45c9..a8106dd270bf 100644
--- a/server/service/integration_enterprise_test.go
+++ b/server/service/integration_enterprise_test.go
@@ -235,6 +235,8 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
MacOSSetupAssistant: optjson.String{Set: true},
BootstrapPackage: optjson.String{Set: true},
EnableReleaseDeviceManually: optjson.SetBool(false),
+ Script: optjson.String{Set: true},
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
},
// because the WindowsSettings was marshalled to JSON to be saved in the DB,
// it did get marshalled, and then when unmarshalled it was set (but
@@ -337,6 +339,8 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
MacOSSetupAssistant: optjson.String{Set: true},
BootstrapPackage: optjson.String{Set: true},
EnableReleaseDeviceManually: optjson.SetBool(false),
+ Script: optjson.String{Set: true},
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -367,6 +371,8 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
MacOSSetupAssistant: optjson.String{Set: true},
BootstrapPackage: optjson.String{Set: true},
EnableReleaseDeviceManually: optjson.SetBool(false),
+ Script: optjson.String{Set: true},
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -399,6 +405,8 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
MacOSSetupAssistant: optjson.String{Set: true},
BootstrapPackage: optjson.String{Set: true},
EnableReleaseDeviceManually: optjson.SetBool(false),
+ Script: optjson.String{Set: true},
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -2365,6 +2373,8 @@ func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() {
MacOSSetupAssistant: optjson.String{Set: true},
BootstrapPackage: optjson.String{Set: true},
EnableReleaseDeviceManually: optjson.SetBool(false),
+ Script: optjson.String{Set: true},
+ Software: optjson.Slice[*fleet.MacOSSetupSoftware]{Set: true, Value: []*fleet.MacOSSetupSoftware{}},
},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
diff --git a/server/service/integration_mdm_dep_test.go b/server/service/integration_mdm_dep_test.go
index b00cd8cd19fd..34a0b3ac934c 100644
--- a/server/service/integration_mdm_dep_test.go
+++ b/server/service/integration_mdm_dep_test.go
@@ -1423,18 +1423,18 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
// Can be useful for debugging
- //switch cmd.Command.RequestType {
- //case "InstallProfile":
- // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, string(fullCmd.Command.InstallProfile.Payload))
- //case "InstallEnterpriseApplication":
- // if fullCmd.Command.InstallEnterpriseApplication.ManifestURL != nil {
- // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, *fullCmd.Command.InstallEnterpriseApplication.ManifestURL)
- // } else {
- // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType)
- // }
- //default:
- // fmt.Println(">>>> device received command: ", cmd.Command.RequestType)
- //}
+ // switch cmd.Command.RequestType {
+ // case "InstallProfile":
+ // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, string(fullCmd.Command.InstallProfile.Payload))
+ // case "InstallEnterpriseApplication":
+ // if fullCmd.Command.InstallEnterpriseApplication.ManifestURL != nil {
+ // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, *fullCmd.Command.InstallEnterpriseApplication.ManifestURL)
+ // } else {
+ // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType)
+ // }
+ // default:
+ // fmt.Println(">>>> device received command: ", cmd.Command.RequestType)
+ // }
cmds = append(cmds, &fullCmd)
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
@@ -1450,14 +1450,15 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
switch cmd.Command.RequestType {
case "InstallProfile":
installProfileCount++
- if strings.Contains(string(cmd.Command.InstallProfile.Payload), "I1") {
+ switch {
+ case strings.Contains(string(cmd.Command.InstallProfile.Payload), "I1"):
profileCustomSeen = true
- } else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetdConfigPayloadIdentifier)) {
+ case strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetdConfigPayloadIdentifier)):
profileFleetdSeen = true
- } else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetCARootConfigPayloadIdentifier)) {
+ case strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetCARootConfigPayloadIdentifier)):
profileFleetCASeen = true
- } else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s%s>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, string(fullCmd.Command.InstallProfile.Payload))
- //case "InstallEnterpriseApplication":
- // if fullCmd.Command.InstallEnterpriseApplication.ManifestURL != nil {
- // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, *fullCmd.Command.InstallEnterpriseApplication.ManifestURL)
- // } else {
- // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType)
- // }
- //default:
- // fmt.Println(">>>> device received command: ", cmd.Command.RequestType)
- //}
+ // switch cmd.Command.RequestType {
+ // case "InstallProfile":
+ // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, string(fullCmd.Command.InstallProfile.Payload))
+ // case "InstallEnterpriseApplication":
+ // if fullCmd.Command.InstallEnterpriseApplication.ManifestURL != nil {
+ // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, *fullCmd.Command.InstallEnterpriseApplication.ManifestURL)
+ // } else {
+ // fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType)
+ // }
+ // default:
+ // fmt.Println(">>>> device received command: ", cmd.Command.RequestType)
+ // }
cmds = append(cmds, &fullCmd)
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
@@ -1639,14 +1640,15 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptFo
switch cmd.Command.RequestType {
case "InstallProfile":
installProfileCount++
- if strings.Contains(string(cmd.Command.InstallProfile.Payload), "I1") {
+ switch {
+ case strings.Contains(string(cmd.Command.InstallProfile.Payload), "I1"):
profileCustomSeen = true
- } else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetdConfigPayloadIdentifier)) {
+ case strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetdConfigPayloadIdentifier)):
profileFleetdSeen = true
- } else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetCARootConfigPayloadIdentifier)) {
+ case strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s", mobileconfig.FleetCARootConfigPayloadIdentifier)):
profileFleetCASeen = true
- } else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("%s%s