From 12d2aba40c351f4b7e70d0eb4ddd4659b27697df Mon Sep 17 00:00:00 2001 From: Nico <32375741+nulmete@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:21:20 -0300 Subject: [PATCH] Fix macOS "Update new hosts to latest" staying enabled in GitOps after clearing version/deadline (#45984) (#47602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Related issue:** Resolves #45984 Fix is applied on the GitOps side since that's what I figured the customer was using on the [Slack thread](https://fleetdm.slack.com/archives/C061ZA91Y1J/p1779372669701129). # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually #### Before Reproduced on `main`: - Set `update_new_hosts: true` beforehand. - Ran `gitops` with `update_new_hosts` commented out. It was still kept as `true`. https://github.com/user-attachments/assets/f6b41f0d-38e6-468f-a605-b3e66b7b2dbc #### After Running `gitops` with `update_new_hosts` commented out switched its value to `false`. https://github.com/user-attachments/assets/24756063-b3a9-400b-a2cc-208dd816a556 ## Summary by CodeRabbit * **Bug Fixes** * Corrected GitOps behavior for the macOS “Update new hosts to latest” setting so it no longer stays enabled after clearing `minimum_version` and `deadline`; it now defaults to disabled unless both are set. * **Tests** * Added GitOps test coverage to verify the defaulting outcomes across YAML variations for the macOS update settings, including explicit and empty field combinations. --- changes/45984-macos-update-new-hosts-default | 1 + cmd/fleetctl/fleetctl/gitops_test.go | 94 ++++++++++++++++++++ server/service/client.go | 11 ++- 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 changes/45984-macos-update-new-hosts-default diff --git a/changes/45984-macos-update-new-hosts-default b/changes/45984-macos-update-new-hosts-default new file mode 100644 index 0000000000..6a4f9f8849 --- /dev/null +++ b/changes/45984-macos-update-new-hosts-default @@ -0,0 +1 @@ +* Fixed an issue where the macOS "Update new hosts to latest" OS update setting could stay enabled in GitOps after `minimum_version` and `deadline` were cleared; when `update_new_hosts` isn't explicitly set, it now defaults to enabled only while a minimum version and deadline are configured. diff --git a/cmd/fleetctl/fleetctl/gitops_test.go b/cmd/fleetctl/fleetctl/gitops_test.go index 763c739988..b626733ba8 100644 --- a/cmd/fleetctl/fleetctl/gitops_test.go +++ b/cmd/fleetctl/fleetctl/gitops_test.go @@ -570,6 +570,100 @@ software: // assert.Equal(t, "hydrant2_secret", h2.ClientSecret) } +func TestGitOpsMacOSUpdateNewHostsDefault(t *testing.T) { + cases := []struct { + name string + currentlyStoredUpdateNewHosts optjson.Bool + macOSUpdatesYAML string + wantUpdateNewHosts optjson.Bool + }{ + { + name: "empty minimum_version and deadline default update_new_hosts to false", + currentlyStoredUpdateNewHosts: optjson.SetBool(true), + macOSUpdatesYAML: ` + macos_updates: + deadline: "" + minimum_version: ""`, + wantUpdateNewHosts: optjson.SetBool(false), + }, + { + name: "configured minimum_version and deadline default update_new_hosts to true", + currentlyStoredUpdateNewHosts: optjson.SetBool(false), + macOSUpdatesYAML: ` + macos_updates: + deadline: "2024-03-03" + minimum_version: "14.6.1"`, + wantUpdateNewHosts: optjson.SetBool(true), + }, + { + // "Update all new hosts to latest" with no minimum version is a valid config, so an + // explicit update_new_hosts: true must be honored even when version/deadline are empty. + name: "explicit update_new_hosts true is honored without minimum_version or deadline", + currentlyStoredUpdateNewHosts: optjson.SetBool(false), + macOSUpdatesYAML: ` + macos_updates: + deadline: "" + minimum_version: "" + update_new_hosts: true`, + wantUpdateNewHosts: optjson.SetBool(true), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)} + _, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{ + License: license, + KeyValueStore: testing_utils.NewMemKeyValueStore(), + }) + + // Mock Apple GDMF API (required for validating OS update minimum version settings). + mdmtest.StartNewAppleGDMFTestServer(t) + + setupEmptyGitOpsMocks(ds) + + storedUpdateNewHosts := c.currentlyStoredUpdateNewHosts + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + cfg := &fleet.AppConfig{} + cfg.MDM.MacOSUpdates.UpdateNewHosts = storedUpdateNewHosts + return cfg, nil + } + savedAppConfig := &fleet.AppConfig{} + ds.SaveAppConfigFunc = func(ctx context.Context, config *fleet.AppConfig) error { + savedAppConfig = config + return nil + } + ds.HasAppleUpdateConfigProfileConfiguredFunc = func(ctx context.Context, teamID uint) (bool, error) { + return false, nil + } + + t.Setenv("FLEET_SERVER_URL", "https://fleet.example.com") + + tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml") + require.NoError(t, err) + _, err = tmpFile.WriteString(` +controls:` + c.macOSUpdatesYAML + ` +queries: +policies: +labels: +agent_options: +org_settings: + server_settings: + server_url: $FLEET_SERVER_URL + org_info: + contact_url: https://example.com/contact + org_name: GitOps Test + secrets: +software: +`) + require.NoError(t, err) + + _ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name()}) + require.Equal(t, c.wantUpdateNewHosts, savedAppConfig.MDM.MacOSUpdates.UpdateNewHosts) + }) + } +} + // TestGitOpsWindowsEntraIDs verifies that Windows Entra tenant and application client IDs round-trip through a // `fleetctl gitops` apply (issue #46388). The client IDs include an upper-case GUID and a case-only duplicate of it, // which exercises server-side normalization: client IDs are authorized case-insensitively, so they are stored diff --git a/server/service/client.go b/server/service/client.go index 2cdbb07f24..9175bce8da 100644 --- a/server/service/client.go +++ b/server/service/client.go @@ -2388,10 +2388,13 @@ func (c *Client) DoGitOps( macOSUpdates["deadline"] = "" } - // To keep things backward compatible, if a minimum_version and deadline are both set but the user hasn't set update_new_hosts, - // then we default update_new_hosts to true - if macOSUpdates["minimum_version"] != "" && macOSUpdates["deadline"] != "" && macOSUpdates["update_new_hosts"] == nil { - macOSUpdates["update_new_hosts"] = true + // When update_new_hosts isn't explicitly set, derive it from whether OS updates + // are configured: default to true when both minimum_version and deadline are set + // (kept for backward compatibility) and false otherwise. Defaulting to false when + // updates aren't configured prevents a previously stored "true" from sticking + // around once minimum_version/deadline are cleared. + if macOSUpdates["update_new_hosts"] == nil { + macOSUpdates["update_new_hosts"] = macOSUpdates["minimum_version"] != "" && macOSUpdates["deadline"] != "" } // Put in default values for ios_updates