diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go index f321e08c04..4b7a1c80dc 100644 --- a/server/datastore/mysql/software_installers.go +++ b/server/datastore/mysql/software_installers.go @@ -2294,6 +2294,19 @@ ON DUPLICATE KEY UPDATE is_active = VALUES(is_active) ` + const updateInstaller = ` +UPDATE + software_installers +SET + install_during_setup = COALESCE(?, install_during_setup), + self_service = ?, + install_script_content_id = ?, + uninstall_script_content_id = ?, + post_install_script_content_id = ?, + pre_install_query = ? +WHERE id = ? +` + const loadSoftwareInstallerID = ` SELECT id @@ -2715,8 +2728,8 @@ WHERE // for this team+title. This prevents duplicate rows from repeated batch sets // that re-download the same latest version. var skipInsert bool + var existingID uint if installer.FleetMaintainedAppID != nil { - var existingID uint err := sqlx.GetContext(ctx, tx, &existingID, ` SELECT id FROM software_installers WHERE global_or_team_id = ? AND title_id = ? AND fleet_maintained_app_id IS NOT NULL AND version = ? @@ -2729,7 +2742,21 @@ WHERE } } - if !skipInsert { + if skipInsert { + // some fields still need to be updated + args := []any{ + installer.InstallDuringSetup, + installer.SelfService, + installScriptID, + uninstallScriptID, + postInstallScriptID, + installer.PreInstallQuery, + existingID, + } + if _, err := tx.ExecContext(ctx, updateInstaller, args...); err != nil { + return ctxerr.Wrapf(ctx, err, "updating existing installer with name %q", installer.Filename) + } + } else { upsertQuery := insertNewOrEditedInstaller if len(existing) > 0 && existing[0].IsPackageModified { // update uploaded_at for updated installer package upsertQuery = fmt.Sprintf("%s, uploaded_at = NOW()", upsertQuery) diff --git a/server/datastore/mysql/software_installers_test.go b/server/datastore/mysql/software_installers_test.go index eb438845ab..d002c74581 100644 --- a/server/datastore/mysql/software_installers_test.go +++ b/server/datastore/mysql/software_installers_test.go @@ -57,6 +57,7 @@ func TestSoftwareInstallers(t *testing.T) { {"SoftwareInstallerReplicaLag", testSoftwareInstallerReplicaLag}, {"SoftwareTitleDisplayName", testSoftwareTitleDisplayName}, {"AddSoftwareTitleToMatchingSoftware", testAddSoftwareTitleToMatchingSoftware}, + {"FleetMaintainedAppInstallerUpdates", testFleetMaintainedAppInstallerUpdates}, } for _, c := range cases { @@ -4275,3 +4276,96 @@ func testAddSoftwareTitleToMatchingSoftware(t *testing.T, ds *Datastore) { }) } + +func testFleetMaintainedAppInstallerUpdates(t *testing.T, ds *Datastore) { + ctx := t.Context() + user := test.NewUser(t, ds, "Alice", "alice@example.com", true) + tfr, err := fleet.NewTempFileReader(strings.NewReader("file contents"), t.TempDir) + require.NoError(t, err) + + maintainedApp, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Maintained1", + Slug: "maintained1", + Platform: "darwin", + UniqueIdentifier: "fleet.maintained1", + }) + require.NoError(t, err) + + installerID, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{ + Title: "testpkg", + Source: "apps", + Platform: "darwin", + PreInstallQuery: "SELECT 1", + InstallScript: "echo install", + PostInstallScript: "echo post install", + UninstallScript: "echo uninstall", + InstallerFile: tfr, + StorageID: "storageid1", + Filename: "test.pkg", + Version: "1.0", + UserID: user.ID, + ValidatedLabels: &fleet.LabelIdentsWithScope{}, + FleetMaintainedAppID: ptr.Uint(maintainedApp.ID), + InstallDuringSetup: ptr.Bool(false), + SelfService: false, + }) + require.NoError(t, err) + + tmFilter := fleet.TeamFilter{User: test.UserAdmin} + titles, _, _, err := ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{TeamID: ptr.Uint(0), Platform: "darwin", AvailableForInstall: true}, tmFilter) + require.NoError(t, err) + require.Len(t, titles, 1) + require.False(t, *titles[0].SoftwarePackage.InstallDuringSetup) + require.False(t, *titles[0].SoftwarePackage.SelfService) + + installer, err := ds.GetSoftwareInstallerMetadataByID(ctx, installerID) + require.NoError(t, err) + require.NotNil(t, installer) + + installScript := installer.InstallScriptContentID + postInstallScript := installer.PostInstallScriptContentID + uninstallScript := installer.UninstallScriptContentID + + require.NotZero(t, installScript) + require.NotZero(t, postInstallScript) + require.NotZero(t, uninstallScript) + require.Equal(t, "SELECT 1", installer.PreInstallQuery) + + // batch add the installer with different scripts, setup experience, self service + err = ds.BatchSetSoftwareInstallers(ctx, nil, []*fleet.UploadSoftwareInstallerPayload{ + { + Title: "testpkg", + Source: "apps", + PreInstallQuery: "SELECT 1 DIFFERENT", + InstallScript: "echo install 2", + PostInstallScript: "echo post install 2", + UninstallScript: "echo uninstall 2", + InstallerFile: tfr, + StorageID: "storageid1", + Filename: "test.pkg", + Version: "1.0", + UserID: user.ID, + ValidatedLabels: &fleet.LabelIdentsWithScope{}, + FleetMaintainedAppID: ptr.Uint(maintainedApp.ID), + InstallDuringSetup: ptr.Bool(true), + SelfService: true, + }, + }) + require.NoError(t, err) + + titles, _, _, err = ds.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{TeamID: ptr.Uint(0), Platform: "darwin", AvailableForInstall: true}, tmFilter) + require.NoError(t, err) + require.Len(t, titles, 1) + require.True(t, *titles[0].SoftwarePackage.InstallDuringSetup) + require.True(t, *titles[0].SoftwarePackage.SelfService) + + installer, err = ds.GetSoftwareInstallerMetadataByID(ctx, installerID) + require.NoError(t, err) + require.NotNil(t, installer) + + // all fields that should have changed did change + require.NotEqual(t, installScript, installer.InstallScriptContentID) + require.NotEqual(t, postInstallScript, installer.PostInstallScriptContentID) + require.NotEqual(t, uninstallScript, installer.UninstallScriptContentID) + require.Equal(t, "SELECT 1 DIFFERENT", installer.PreInstallQuery) +}