GitOps: support categories for in-house apps gitops (#35531)

This commit is contained in:
Martin Angers
2025-11-12 09:06:16 -05:00
committed by GitHub
parent 22e6dcd5e2
commit 4e97263881
3 changed files with 102 additions and 36 deletions
+54
View File
@@ -941,6 +941,31 @@ WHERE
in_house_app_id = ?
`
const deleteAllInHouseCategories = `
DELETE FROM
in_house_app_software_categories
WHERE
in_house_app_id = ?
`
const deleteInHouseCategoriesNotInList = `
DELETE FROM
in_house_app_software_categories
WHERE
in_house_app_id = ? AND
software_category_id NOT IN (?)
`
const upsertInHouseCategories = `
INSERT IGNORE INTO
in_house_app_software_categories (
in_house_app_id,
software_category_id
)
VALUES
%s
`
// use a team id of 0 if no-team
var globalOrTeamID uint
if tmID != nil {
@@ -1197,6 +1222,35 @@ WHERE
}
}
if len(installer.CategoryIDs) == 0 {
// delete all categories if there are any
_, err := tx.ExecContext(ctx, deleteAllInHouseCategories, installerID)
if err != nil {
return ctxerr.Wrapf(ctx, err, "delete in-house categories for %s", installer.Filename)
}
} else {
// there are new categories to apply, delete only the obsolete ones
stmt, args, err := sqlx.In(deleteInHouseCategoriesNotInList, installerID, installer.CategoryIDs)
if err != nil {
return ctxerr.Wrap(ctx, err, "build statement to delete in-house categories not in list")
}
_, err = tx.ExecContext(ctx, stmt, args...)
if err != nil {
return ctxerr.Wrapf(ctx, err, "delete in-house categories not in list for %s", installer.Filename)
}
var upsertCategoriesArgs []any
for _, catID := range installer.CategoryIDs {
upsertCategoriesArgs = append(upsertCategoriesArgs, installerID, catID)
}
upsertCategoriesValues := strings.TrimSuffix(strings.Repeat("(?,?),", len(installer.CategoryIDs)), ",")
_, err = tx.ExecContext(ctx, fmt.Sprintf(upsertInHouseCategories, upsertCategoriesValues), upsertCategoriesArgs...)
if err != nil {
return ctxerr.Wrapf(ctx, err, "insert new/edited categories for in-house with name %q", installer.Filename)
}
}
// perform side effects if this was an update (related to pending install requests)
if len(existing) > 0 {
affectedHostIDs, err := ds.runInHouseUpdateSideEffectsInTransaction(
+47 -35
View File
@@ -464,7 +464,7 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
nanoEnroll(t, ds, host2, false)
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
assertTitlesAndApps := func(wantTitles []fleet.SoftwareTitleListResult, wantApps []fleet.InHouseAppPayload) {
assertTitlesAndApps := func(wantTitles []fleet.SoftwareTitleListResult, wantApps []fleet.SoftwareInstaller) {
tmFilter := fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}}
titles, _, _, err := ds.ListSoftwareTitles(
ctx,
@@ -497,19 +497,20 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
sort.Slice(wantApps, func(i, j int) bool {
l, r := wantApps[i], wantApps[j]
return l.Filename < r.Filename || l.Filename == r.Filename && l.Platform < r.Platform
return l.Name < r.Name || l.Name == r.Name && l.Platform < r.Platform
})
require.Len(t, wantApps, len(titleIDs))
for i, want := range wantApps {
got, err := ds.GetInHouseAppMetadataByTeamAndTitleID(ctx, &team.ID, titleIDs[i])
require.NoError(t, err)
require.Equal(t, want.Filename, got.Name)
require.Equal(t, want.Name, got.Name)
require.Equal(t, want.Platform, got.Platform)
require.Equal(t, want.Version, got.Version)
require.Equal(t, want.StorageID, got.StorageID)
require.Equal(t, want.SelfService, got.SelfService)
require.Equal(t, want.BundleID, got.BundleIdentifier)
require.Equal(t, want.BundleIdentifier, got.BundleIdentifier)
require.ElementsMatch(t, want.Categories, got.Categories)
}
}
@@ -582,9 +583,9 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
assertTitlesAndApps([]fleet.SoftwareTitleListResult{
{Name: ipa1.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa1")},
{Name: ipa1.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa1")},
}, []fleet.InHouseAppPayload{
{Filename: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
})
// add a new installer + ipa1 installer
@@ -675,11 +676,11 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
{Name: ipa1.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa1")},
{Name: ipa2.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa2")},
{Name: ipa2.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa2")},
}, []fleet.InHouseAppPayload{
{Filename: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier},
{Name: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier},
})
// rerun with no change
@@ -748,15 +749,20 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
{Name: ipa1.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa1")},
{Name: ipa2.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa2")},
{Name: ipa2.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa2")},
}, []fleet.InHouseAppPayload{
{Filename: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier},
{Name: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier},
})
// change ipa2 self-service
// change ipa2 self-service and add categories
ipa2.SelfService = !ipa2.SelfService
ipa2.Categories = []string{"Communication", "Productivity"}
catIDs, err := ds.GetSoftwareCategoryIDs(ctx, ipa2.Categories)
require.NoError(t, err)
ipa2.CategoryIDs = catIDs
err = ds.BatchSetInHouseAppsInstallers(ctx, &team.ID, []*fleet.UploadSoftwareInstallerPayload{
{
StorageID: ipa1.StorageID,
@@ -796,6 +802,7 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
ValidatedLabels: &fleet.LabelIdentsWithScope{},
BundleIdentifier: ipa2.BundleIdentifier,
SelfService: ipa2.SelfService,
CategoryIDs: ipa2.CategoryIDs,
},
{
StorageID: ipa2.StorageID,
@@ -809,6 +816,7 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
ValidatedLabels: &fleet.LabelIdentsWithScope{},
BundleIdentifier: ipa2.BundleIdentifier,
SelfService: ipa2.SelfService,
CategoryIDs: ipa2.CategoryIDs,
},
})
require.NoError(t, err)
@@ -822,11 +830,11 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
{Name: ipa1.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa1")},
{Name: ipa2.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa2")},
{Name: ipa2.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa2")},
}, []fleet.InHouseAppPayload{
{Filename: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier, Categories: ipa2.Categories},
{Name: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier, Categories: ipa2.Categories},
})
// remove ipa1
@@ -843,6 +851,7 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
ValidatedLabels: &fleet.LabelIdentsWithScope{},
BundleIdentifier: ipa2.BundleIdentifier,
SelfService: ipa2.SelfService,
CategoryIDs: ipa2.CategoryIDs,
},
{
StorageID: ipa2.StorageID,
@@ -856,6 +865,7 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
ValidatedLabels: &fleet.LabelIdentsWithScope{},
BundleIdentifier: ipa2.BundleIdentifier,
SelfService: ipa2.SelfService,
CategoryIDs: ipa2.CategoryIDs,
},
})
require.NoError(t, err)
@@ -878,9 +888,9 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
assertTitlesAndApps([]fleet.SoftwareTitleListResult{
{Name: ipa2.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa2")},
{Name: ipa2.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa2")},
}, []fleet.InHouseAppPayload{
{Filename: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier, Categories: ipa2.Categories},
{Name: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier, Categories: ipa2.Categories},
})
// add pending and completed installs for ipa2
@@ -906,6 +916,7 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
ValidatedLabels: &fleet.LabelIdentsWithScope{},
BundleIdentifier: ipa2.BundleIdentifier,
SelfService: ipa2.SelfService,
CategoryIDs: ipa2.CategoryIDs,
},
{
StorageID: ipa2.StorageID,
@@ -919,6 +930,7 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
ValidatedLabels: &fleet.LabelIdentsWithScope{},
BundleIdentifier: ipa2.BundleIdentifier,
SelfService: ipa2.SelfService,
CategoryIDs: ipa2.CategoryIDs,
},
})
require.NoError(t, err)
@@ -929,9 +941,9 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
assertTitlesAndApps([]fleet.SoftwareTitleListResult{
{Name: ipa2.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa2")},
{Name: ipa2.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa2")},
}, []fleet.InHouseAppPayload{
{Filename: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
{Filename: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleID: ipa2.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa2.Filename, Platform: "ios", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier, Categories: ipa2.Categories},
{Name: ipa2.Filename, Platform: "ipados", Version: ipa2.Version, StorageID: ipa2.StorageID, SelfService: ipa2.SelfService, BundleIdentifier: ipa2.BundleIdentifier, Categories: ipa2.Categories},
})
summary, err = ds.GetSummaryHostInHouseAppInstalls(ctx, &team.ID, iosInHouseID2)
@@ -987,9 +999,9 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
assertTitlesAndApps([]fleet.SoftwareTitleListResult{
{Name: ipa1.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa1")},
{Name: ipa1.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa1")},
}, []fleet.InHouseAppPayload{
{Filename: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
})
// stats don't report anything about ipa2 anymore
@@ -1052,9 +1064,9 @@ func testBatchSetInHouseInstallers(t *testing.T, ds *Datastore) {
assertTitlesAndApps([]fleet.SoftwareTitleListResult{
{Name: ipa1.Title, Source: "ios_apps", BundleIdentifier: ptr.String("com.ipa1")},
{Name: ipa1.Title, Source: "ipados_apps", BundleIdentifier: ptr.String("com.ipa1")},
}, []fleet.InHouseAppPayload{
{Filename: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
{Filename: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleID: ipa1.BundleIdentifier},
}, []fleet.SoftwareInstaller{
{Name: ipa1.Filename, Platform: "ios", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
{Name: ipa1.Filename, Platform: "ipados", Version: ipa1.Version, StorageID: ipa1.StorageID, SelfService: ipa1.SelfService, BundleIdentifier: ipa1.BundleIdentifier},
})
// stats don't report anything about ipa1 anymore (as if it was deleted)
+1 -1
View File
@@ -5644,7 +5644,7 @@ FROM
JOIN in_house_app_software_categories ihasc ON ihasc.in_house_app_id = iha.id
JOIN software_categories sc ON ihasc.software_category_id = sc.id
WHERE
st.id IN (?) AND iha.global_or_team_id = ?;
st.id IN (?) AND iha.global_or_team_id = ?
`
var tmID uint