diff --git a/server/datastore/cached_mysql/cached_mysql.go b/server/datastore/cached_mysql/cached_mysql.go index c6d0cc172a..9f86f9dbdc 100644 --- a/server/datastore/cached_mysql/cached_mysql.go +++ b/server/datastore/cached_mysql/cached_mysql.go @@ -65,6 +65,12 @@ const ( // changes, it'll linger for this amount of time. The curent // implementation assumes infrequent asset changes. defaultMDMConfigAssetExpiration = 15 * time.Minute + + // FMA names cache stores a map of unique_identifier -> canonical name + // for Fleet-maintained apps. Used during software ingestion to override + // osquery-reported names with the FMA canonical name. + fmaNamesByIdentifierKey = "FMANamesByIdentifier" + defaultFMANamesByIdentifierExpiration = 5 * time.Minute ) // cloneCache wraps the in memory cache with one that clones items before returning them. @@ -114,17 +120,18 @@ type cachedMysql struct { c *cloneCache - appConfigExp time.Duration - packsExp time.Duration - scheduledQueriesExp time.Duration - teamAgentOptionsExp time.Duration - teamFeaturesExp time.Duration - teamMDMConfigExp time.Duration - defaultTeamConfigExp time.Duration - queryByNameExp time.Duration - queryResultsCountExp time.Duration - yaraRuleByNameExp time.Duration - mdmConfigAssetExp time.Duration + appConfigExp time.Duration + packsExp time.Duration + scheduledQueriesExp time.Duration + teamAgentOptionsExp time.Duration + teamFeaturesExp time.Duration + teamMDMConfigExp time.Duration + defaultTeamConfigExp time.Duration + queryByNameExp time.Duration + queryResultsCountExp time.Duration + yaraRuleByNameExp time.Duration + mdmConfigAssetExp time.Duration + fmaNamesByIdentifierExp time.Duration } type Option func(*cachedMysql) @@ -195,21 +202,28 @@ func WithDefaultTeamConfigExpiration(d time.Duration) Option { } } +func WithFMANamesByIdentifierExpiration(d time.Duration) Option { + return func(o *cachedMysql) { + o.fmaNamesByIdentifierExp = d + } +} + func New(ds fleet.Datastore, opts ...Option) fleet.Datastore { c := &cachedMysql{ - Datastore: ds, - c: &cloneCache{cache.New(5*time.Minute, 10*time.Minute)}, - appConfigExp: defaultAppConfigExpiration, - packsExp: defaultPacksExpiration, - scheduledQueriesExp: defaultScheduledQueriesExpiration, - teamAgentOptionsExp: defaultTeamAgentOptionsExpiration, - teamFeaturesExp: defaultTeamFeaturesExpiration, - teamMDMConfigExp: defaultTeamMDMConfigExpiration, - defaultTeamConfigExp: defaultDefaultTeamConfigExpiration, - queryByNameExp: defaultQueryByNameExpiration, - queryResultsCountExp: defaultQueryResultsCountExpiration, - yaraRuleByNameExp: defaultYaraRuleByNameExpiration, - mdmConfigAssetExp: defaultMDMConfigAssetExpiration, + Datastore: ds, + c: &cloneCache{cache.New(5*time.Minute, 10*time.Minute)}, + appConfigExp: defaultAppConfigExpiration, + packsExp: defaultPacksExpiration, + scheduledQueriesExp: defaultScheduledQueriesExpiration, + teamAgentOptionsExp: defaultTeamAgentOptionsExpiration, + teamFeaturesExp: defaultTeamFeaturesExpiration, + teamMDMConfigExp: defaultTeamMDMConfigExpiration, + defaultTeamConfigExp: defaultDefaultTeamConfigExpiration, + queryByNameExp: defaultQueryByNameExpiration, + queryResultsCountExp: defaultQueryResultsCountExpiration, + yaraRuleByNameExp: defaultYaraRuleByNameExpiration, + mdmConfigAssetExp: defaultMDMConfigAssetExpiration, + fmaNamesByIdentifierExp: defaultFMANamesByIdentifierExpiration, } for _, fn := range opts { fn(c) @@ -541,3 +555,44 @@ func (ds *cachedMysql) ApplyYaraRules(ctx context.Context, rules []fleet.YaraRul return nil } + +func (ds *cachedMysql) GetFMANamesByIdentifier(ctx context.Context) (map[string]string, error) { + if x, found := ds.c.Get(ctx, fmaNamesByIdentifierKey); found { + if names, ok := x.(fmaNameMap); ok { + return names, nil + } + } + + names, err := ds.Datastore.GetFMANamesByIdentifier(ctx) + if err != nil { + return nil, err + } + + ds.c.Set(ctx, fmaNamesByIdentifierKey, fmaNameMap(names), ds.fmaNamesByIdentifierExp) + + return names, nil +} + +func (ds *cachedMysql) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) { + result, err := ds.Datastore.UpsertMaintainedApp(ctx, app) + if err != nil { + return nil, err + } + + // Invalidate the FMA names cache since an app was added/updated + ds.c.Delete(fmaNamesByIdentifierKey) + + return result, nil +} + +func (ds *cachedMysql) ClearRemovedFleetMaintainedApps(ctx context.Context, slugsToKeep []string) error { + err := ds.Datastore.ClearRemovedFleetMaintainedApps(ctx, slugsToKeep) + if err != nil { + return err + } + + // Invalidate the FMA names cache since apps may have been removed + ds.c.Delete(fmaNamesByIdentifierKey) + + return nil +} diff --git a/server/datastore/cached_mysql/cached_mysql_test.go b/server/datastore/cached_mysql/cached_mysql_test.go index 5ab88b4a28..ef1bb8008c 100644 --- a/server/datastore/cached_mysql/cached_mysql_test.go +++ b/server/datastore/cached_mysql/cached_mysql_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "maps" "testing" "time" @@ -981,3 +982,80 @@ func TestCachedYaraRules(t *testing.T) { require.Same(t, testRule1, rule1Expired) require.True(t, mockedDS.YaraRuleByNameFuncInvoked) // from DB after expiration } + +func TestCachedFMANamesByIdentifier(t *testing.T) { + t.Parallel() + + mockedDS := new(mock.Store) + ds := New(mockedDS, WithFMANamesByIdentifierExpiration(100*time.Millisecond)) + + fmaNames := map[string]string{ + "com.microsoft.VSCode": "Microsoft Visual Studio Code", + "com.1password.1password": "1Password", + } + + mockedDS.GetFMANamesByIdentifierFunc = func(ctx context.Context) (map[string]string, error) { + // Return a copy to avoid mutation + result := make(map[string]string, len(fmaNames)) + maps.Copy(result, fmaNames) + return result, nil + } + + mockedDS.UpsertMaintainedAppFunc = func(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) { + return app, nil + } + + // Test 1: Initial call hits the DB + names, err := ds.GetFMANamesByIdentifier(context.Background()) + require.NoError(t, err) + require.Len(t, names, 2) + require.Equal(t, "Microsoft Visual Studio Code", names["com.microsoft.VSCode"]) + require.Equal(t, "1Password", names["com.1password.1password"]) + require.True(t, mockedDS.GetFMANamesByIdentifierFuncInvoked) + mockedDS.GetFMANamesByIdentifierFuncInvoked = false + + // Test 2: Second call uses cache + names2, err := ds.GetFMANamesByIdentifier(context.Background()) + require.NoError(t, err) + require.Len(t, names2, 2) + require.False(t, mockedDS.GetFMANamesByIdentifierFuncInvoked) // from cache + + // Test 3: Modifying returned map doesn't affect cache + names2["com.microsoft.VSCode"] = "Modified" + names3, err := ds.GetFMANamesByIdentifier(context.Background()) + require.NoError(t, err) + require.Equal(t, "Microsoft Visual Studio Code", names3["com.microsoft.VSCode"]) // still original + + // Test 4: UpsertMaintainedApp invalidates cache + _, err = ds.UpsertMaintainedApp(context.Background(), &fleet.MaintainedApp{ + Name: "New App", + Slug: "new-app/darwin", + Platform: "darwin", + UniqueIdentifier: "com.new.app", + }) + require.NoError(t, err) + require.True(t, mockedDS.UpsertMaintainedAppFuncInvoked) + + // Update mock to return new data + fmaNames["com.new.app"] = "New App" + + // Next call should hit DB again since cache was invalidated + names4, err := ds.GetFMANamesByIdentifier(context.Background()) + require.NoError(t, err) + require.Len(t, names4, 3) + require.Equal(t, "New App", names4["com.new.app"]) + require.True(t, mockedDS.GetFMANamesByIdentifierFuncInvoked) + mockedDS.GetFMANamesByIdentifierFuncInvoked = false + + // Test 5: Cache expiration + time.Sleep(200 * time.Millisecond) + + // Update mock to return different data + fmaNames["com.microsoft.VSCode"] = "VS Code Updated" + + // This call should get from DB again since cache expired + names5, err := ds.GetFMANamesByIdentifier(context.Background()) + require.NoError(t, err) + require.Equal(t, "VS Code Updated", names5["com.microsoft.VSCode"]) + require.True(t, mockedDS.GetFMANamesByIdentifierFuncInvoked) +} diff --git a/server/datastore/cached_mysql/cloners.go b/server/datastore/cached_mysql/cloners.go index 64ad56d06e..f9447168d9 100644 --- a/server/datastore/cached_mysql/cloners.go +++ b/server/datastore/cached_mysql/cloners.go @@ -2,6 +2,7 @@ package cached_mysql import ( "encoding/json" + "maps" "github.com/fleetdm/fleet/v4/server/fleet" ) @@ -39,3 +40,16 @@ type integer int func (i integer) Clone() (fleet.Cloner, error) { return i, nil } + +// fmaNameMap is a map of unique_identifier -> canonical FMA name. +// Used during software ingestion to override osquery-reported names. +type fmaNameMap map[string]string + +func (m fmaNameMap) Clone() (fleet.Cloner, error) { + if m == nil { + return fmaNameMap(nil), nil + } + clone := make(fmaNameMap, len(m)) + maps.Copy(clone, m) + return clone, nil +} diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go index c1754a4672..6b593b01e5 100644 --- a/server/datastore/mysql/maintained_apps.go +++ b/server/datastore/mysql/maintained_apps.go @@ -34,6 +34,40 @@ ON DUPLICATE KEY UPDATE } id, _ := res.LastInsertId() appID = uint(id) //nolint:gosec // dismiss G115 + + // For darwin apps, update existing software_titles and software entries + // to use the FMA canonical name. This ensures consistency when an FMA + // is added for software that was previously ingested with osquery-reported names. + // + // We only run these UPDATEs when the FMA was actually inserted or modified. + // MySQL's ON DUPLICATE KEY UPDATE returns RowsAffected: + // 0 = duplicate key, no changes (existing FMA with same values) + // 1 = new row inserted + // 2 = duplicate key, values changed + // Skip if RowsAffected == 0 since nothing changed. + rowsAffected, _ := res.RowsAffected() + if app.Platform == "darwin" && app.UniqueIdentifier != "" && rowsAffected > 0 { + _, err = tx.ExecContext(ctx, ` + UPDATE software_titles + SET name = ? + WHERE bundle_identifier = ? + AND name != ? + `, app.Name, app.UniqueIdentifier, app.Name) + if err != nil { + return ctxerr.Wrap(ctx, err, "update software_titles names for FMA") + } + + _, err = tx.ExecContext(ctx, ` + UPDATE software + SET name = ? + WHERE bundle_identifier = ? + AND name != ? + `, app.Name, app.UniqueIdentifier, app.Name) + if err != nil { + return ctxerr.Wrap(ctx, err, "update software names for FMA") + } + } + return nil }) if err != nil { @@ -180,6 +214,30 @@ func (ds *Datastore) ListAvailableFleetMaintainedApps(ctx context.Context, teamI return avail, meta, nil } +func (ds *Datastore) GetFMANamesByIdentifier(ctx context.Context) (map[string]string, error) { + query := `SELECT unique_identifier, name FROM fleet_maintained_apps WHERE platform = 'darwin'` + + rows, err := ds.reader(ctx).QueryContext(ctx, query) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "query FMA names by identifier") + } + defer rows.Close() + + result := make(map[string]string) + for rows.Next() { + var identifier, name string + if err := rows.Scan(&identifier, &name); err != nil { + return nil, ctxerr.Wrap(ctx, err, "scan FMA name row") + } + result[identifier] = name + } + if err := rows.Err(); err != nil { + return nil, ctxerr.Wrap(ctx, err, "iterate FMA name rows") + } + + return result, nil +} + func (ds *Datastore) ClearRemovedFleetMaintainedApps(ctx context.Context, slugsToKeep []string) error { stmt := `DELETE FROM fleet_maintained_apps WHERE slug NOT IN (?)` diff --git a/server/datastore/mysql/maintained_apps_test.go b/server/datastore/mysql/maintained_apps_test.go index 80048873bb..49128ee6e7 100644 --- a/server/datastore/mysql/maintained_apps_test.go +++ b/server/datastore/mysql/maintained_apps_test.go @@ -25,6 +25,8 @@ func TestMaintainedApps(t *testing.T) { {"SyncAndRemoveApps", testSyncAndRemoveApps}, {"GetMaintainedAppBySlug", testGetMaintainedAppBySlug}, {"ListAvailableAppsWindows", testListAvailableAppsWindows}, + {"GetFMANamesByIdentifier", testGetFMANamesByIdentifier}, + {"UpsertMaintainedAppUpdatesSoftware", testUpsertMaintainedAppUpdatesSoftware}, } for _, c := range cases { @@ -629,3 +631,176 @@ func testListAvailableAppsWindows(t *testing.T, ds *Datastore) { // the darwin app should not be matched by name require.Nil(t, apps[1].TitleID) } + +func testUpsertMaintainedAppUpdatesSoftware(t *testing.T, ds *Datastore) { + ctx := t.Context() + + // Create a host to associate software with + host, err := ds.NewHost(ctx, &fleet.Host{ + Hostname: "test-host", + Platform: "darwin", + OsqueryHostID: ptr.String("osquery-host-id"), + NodeKey: ptr.String("node-key"), + DetailUpdatedAt: ds.clock.Now(), + LabelUpdatedAt: ds.clock.Now(), + PolicyUpdatedAt: ds.clock.Now(), + SeenTime: ds.clock.Now(), + }) + require.NoError(t, err) + + // Create software entries with osquery-reported name ("Code" instead of "Microsoft Visual Studio Code") + software := []fleet.Software{ + { + Name: "Code", + Version: "1.85.0", + Source: "apps", + BundleIdentifier: "com.microsoft.VSCode", + }, + { + Name: "Code", + Version: "1.84.0", + Source: "apps", + BundleIdentifier: "com.microsoft.VSCode", + }, + } + + // Insert software using the normal ingestion path + _, err = ds.UpdateHostSoftware(ctx, host.ID, software) + require.NoError(t, err) + + // Verify the software and software_titles were created with the osquery name "Code" + var softwareNames []string + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.SelectContext(ctx, q, &softwareNames, + `SELECT name FROM software WHERE bundle_identifier = 'com.microsoft.VSCode' ORDER BY version`) + }) + require.Len(t, softwareNames, 2) + require.Equal(t, "Code", softwareNames[0]) + require.Equal(t, "Code", softwareNames[1]) + + var titleName string + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &titleName, + `SELECT name FROM software_titles WHERE bundle_identifier = 'com.microsoft.VSCode'`) + }) + require.Equal(t, "Code", titleName) + + // Now upsert an FMA with the canonical name + _, err = ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Microsoft Visual Studio Code", + Slug: "visual-studio-code/darwin", + Platform: "darwin", + UniqueIdentifier: "com.microsoft.VSCode", + }) + require.NoError(t, err) + + // Verify software entries were updated to use the FMA canonical name + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.SelectContext(ctx, q, &softwareNames, + `SELECT name FROM software WHERE bundle_identifier = 'com.microsoft.VSCode' ORDER BY version`) + }) + require.Len(t, softwareNames, 2) + require.Equal(t, "Microsoft Visual Studio Code", softwareNames[0]) + require.Equal(t, "Microsoft Visual Studio Code", softwareNames[1]) + + // Verify software_titles was also updated + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &titleName, + `SELECT name FROM software_titles WHERE bundle_identifier = 'com.microsoft.VSCode'`) + }) + require.Equal(t, "Microsoft Visual Studio Code", titleName) + + // Verify upserting the same FMA again doesn't cause issues (idempotent) + _, err = ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Microsoft Visual Studio Code", + Slug: "visual-studio-code/darwin", + Platform: "darwin", + UniqueIdentifier: "com.microsoft.VSCode", + }) + require.NoError(t, err) + + // Names should still be the FMA canonical name + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.SelectContext(ctx, q, &softwareNames, + `SELECT name FROM software WHERE bundle_identifier = 'com.microsoft.VSCode' ORDER BY version`) + }) + require.Len(t, softwareNames, 2) + require.Equal(t, "Microsoft Visual Studio Code", softwareNames[0]) + require.Equal(t, "Microsoft Visual Studio Code", softwareNames[1]) + + // Verify Windows FMA does NOT update darwin software entries + // First create darwin software with a different bundle_id + software2 := []fleet.Software{ + { + Name: "Some App", + Version: "1.0.0", + Source: "apps", + BundleIdentifier: "com.example.someapp", + }, + } + _, err = ds.UpdateHostSoftware(ctx, host.ID, append(software, software2...)) + require.NoError(t, err) + + // Upsert a Windows FMA - should not affect darwin software + _, err = ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Some App Windows", + Slug: "some-app/windows", + Platform: "windows", + UniqueIdentifier: "com.example.someapp", // Same identifier but different platform + }) + require.NoError(t, err) + + // The darwin software should NOT have been renamed + var someAppName string + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &someAppName, + `SELECT name FROM software WHERE bundle_identifier = 'com.example.someapp'`) + }) + require.Equal(t, "Some App", someAppName) +} + +func testGetFMANamesByIdentifier(t *testing.T, ds *Datastore) { + ctx := t.Context() + + // Initially empty + names, err := ds.GetFMANamesByIdentifier(ctx) + require.NoError(t, err) + require.Empty(t, names) + + // Add some darwin FMAs + _, err = ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Microsoft Visual Studio Code", + Slug: "visual-studio-code/darwin", + Platform: "darwin", + UniqueIdentifier: "com.microsoft.VSCode", + }) + require.NoError(t, err) + + _, err = ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "1Password", + Slug: "1password/darwin", + Platform: "darwin", + UniqueIdentifier: "com.1password.1password", + }) + require.NoError(t, err) + + // Add a Windows FMA - should NOT be returned (only darwin) + _, err = ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Microsoft Visual Studio Code", + Slug: "visual-studio-code/windows", + Platform: "windows", + UniqueIdentifier: "Microsoft Visual Studio Code", + }) + require.NoError(t, err) + + // Get FMA names - should only return darwin apps + names, err = ds.GetFMANamesByIdentifier(ctx) + require.NoError(t, err) + require.Len(t, names, 2) + require.Equal(t, "Microsoft Visual Studio Code", names["com.microsoft.VSCode"]) + require.Equal(t, "1Password", names["com.1password.1password"]) + + // Windows identifier should not be present + _, ok := names["Microsoft Visual Studio Code"] + require.False(t, ok) +} diff --git a/server/datastore/mysql/migrations/tables/20260326210603_UpdateSoftwareTitleNamesToFMANames.go b/server/datastore/mysql/migrations/tables/20260326210603_UpdateSoftwareTitleNamesToFMANames.go new file mode 100644 index 0000000000..9e42f31188 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20260326210603_UpdateSoftwareTitleNamesToFMANames.go @@ -0,0 +1,55 @@ +package tables + +import "database/sql" + +func init() { + MigrationClient.AddMigration(Up_20260326210603, Down_20260326210603) +} + +func Up_20260326210603(tx *sql.Tx) error { + // Update software_titles to use FMA canonical names where there's a matching + // bundle_identifier. This fixes existing titles that were created with + // osquery-reported names (e.g., "Code") instead of the FMA name + // (e.g., "Microsoft Visual Studio Code"). + // + // Note: We intentionally do NOT add an index on software.bundle_identifier. + // The software table is a hot table with frequent writes (software ingestion + // runs per-host/hour). Adding an index would impose write overhead on every + // ingestion. The cost of a full table scan here (one-time migration) and during + // rare FMA additions is acceptable compared to continuous index maintenance. + // + // software_titles.bundle_identifier already has an index (idx_software_titles_bundle_identifier). + _, err := tx.Exec(` + UPDATE software_titles st + JOIN fleet_maintained_apps fma + ON st.bundle_identifier = fma.unique_identifier + AND fma.platform = 'darwin' + SET st.name = fma.name + WHERE st.bundle_identifier IS NOT NULL + AND st.bundle_identifier != '' + AND st.name != fma.name + `) + if err != nil { + return err + } + + // Also update software entries to match their software_titles names. + // This ensures consistency when navigating from software_titles to software versions. + _, err = tx.Exec(` + UPDATE software s + JOIN fleet_maintained_apps fma + ON s.bundle_identifier = fma.unique_identifier + AND fma.platform = 'darwin' + SET s.name = fma.name + WHERE s.bundle_identifier IS NOT NULL + AND s.bundle_identifier != '' + AND s.name != fma.name + `) + return err +} + +func Down_20260326210603(tx *sql.Tx) error { + // Down migration is a no-op because we cannot reliably restore the original + // osquery-reported names. The FMA names are the canonical/correct names anyway. + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20260326210603_UpdateSoftwareTitleNamesToFMANames_test.go b/server/datastore/mysql/migrations/tables/20260326210603_UpdateSoftwareTitleNamesToFMANames_test.go new file mode 100644 index 0000000000..1745dc1ec5 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20260326210603_UpdateSoftwareTitleNamesToFMANames_test.go @@ -0,0 +1,76 @@ +package tables + +import ( + "testing" + + "github.com/fleetdm/fleet/v4/server/ptr" + "github.com/stretchr/testify/require" +) + +func TestUp_20260326210603(t *testing.T) { + db := applyUpToPrev(t) + + // Insert FMAs with canonical names + dataStmts := ` + INSERT INTO fleet_maintained_apps (name, slug, unique_identifier, platform) VALUES + ('Microsoft Visual Studio Code', 'visual-studio-code/darwin', 'com.microsoft.VSCode', 'darwin'), + ('1Password', '1password/darwin', 'com.1password.1password', 'darwin'), + ('Windows App', 'windows-app/windows', 'com.windows.app', 'windows'); + + INSERT INTO software_titles (id, name, source, bundle_identifier) VALUES + (1, 'Code', 'apps', 'com.microsoft.VSCode'), + (2, '1Password 7', 'apps', 'com.1password.1password'), + (3, 'Other App', 'apps', 'com.example.other'), + (4, 'No Bundle ID App', 'apps', NULL); + + INSERT INTO software (id, checksum, name, version, source, bundle_identifier, title_id) VALUES + (1, 'checksum_01', 'Code', '1.85.0', 'apps', 'com.microsoft.VSCode', 1), + (2, 'checksum_02', 'Code', '1.86.0', 'apps', 'com.microsoft.VSCode', 1), + (3, 'checksum_03', '1Password 7', '7.10.0', 'apps', 'com.1password.1password', 2), + (4, 'checksum_04', 'Other App', '1.0.0', 'apps', 'com.example.other', 3), + (5, 'checksum_05', 'No Bundle ID App', '1.0.0', 'apps', '', 4); + ` + + _, err := db.Exec(dataStmts) + require.NoError(t, err) + + // Apply the migration + applyNext(t, db) + + // Verify software_titles were updated correctly + type softwareTitle struct { + ID uint `db:"id"` + Name string `db:"name"` + BundleIdentifier string `db:"bundle_identifier"` + } + + var titles []softwareTitle + err = db.Select(&titles, `SELECT id, name, COALESCE(bundle_identifier, '') as bundle_identifier FROM software_titles ORDER BY id`) + require.NoError(t, err) + require.ElementsMatch(t, []softwareTitle{ + {1, "Microsoft Visual Studio Code", "com.microsoft.VSCode"}, // Updated to FMA name + {2, "1Password", "com.1password.1password"}, // Updated to FMA name + {3, "Other App", "com.example.other"}, // No matching FMA, unchanged + {4, "No Bundle ID App", ""}, // No bundle_identifier, unchanged + }, titles) + + // Verify software entries were updated correctly + type softwareRow struct { + ID uint `db:"id"` + Name string `db:"name"` + Version string `db:"version"` + BundleIdentifier string `db:"bundle_identifier"` + TitleID *uint `db:"title_id"` + } + + var software []softwareRow + err = db.Select(&software, `SELECT id, name, version, COALESCE(bundle_identifier, '') as bundle_identifier, title_id FROM software ORDER BY id`) + require.NoError(t, err) + require.ElementsMatch(t, []softwareRow{ + {1, "Microsoft Visual Studio Code", "1.85.0", "com.microsoft.VSCode", ptr.Uint(1)}, // Updated to FMA name + {2, "Microsoft Visual Studio Code", "1.86.0", "com.microsoft.VSCode", ptr.Uint(1)}, // Updated to FMA name + {3, "1Password", "7.10.0", "com.1password.1password", ptr.Uint(2)}, // Updated to FMA name + {4, "Other App", "1.0.0", "com.example.other", ptr.Uint(3)}, // No matching FMA, unchanged + {5, "No Bundle ID App", "1.0.0", "", ptr.Uint(4)}, // No bundle_identifier, unchanged + }, software) +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 9b8cf3396b..86e6144f26 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1807,9 +1807,9 @@ CREATE TABLE `migration_status_tables` ( `is_applied` tinyint(1) NOT NULL, `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=503 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=504 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01'),(393,20250609112613,1,'2020-01-01 01:01:01'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260314120000,1,'2020-01-01 01:01:01'),(484,20260316120000,1,'2020-01-01 01:01:01'),(485,20260316120001,1,'2020-01-01 01:01:01'),(486,20260316120002,1,'2020-01-01 01:01:01'),(487,20260316120003,1,'2020-01-01 01:01:01'),(488,20260316120004,1,'2020-01-01 01:01:01'),(489,20260316120005,1,'2020-01-01 01:01:01'),(490,20260316120006,1,'2020-01-01 01:01:01'),(491,20260316120007,1,'2020-01-01 01:01:01'),(492,20260316120008,1,'2020-01-01 01:01:01'),(493,20260316120009,1,'2020-01-01 01:01:01'),(494,20260316120010,1,'2020-01-01 01:01:01'),(495,20260316120011,1,'2020-01-01 01:01:01'),(496,20260317120000,1,'2020-01-01 01:01:01'),(497,20260318184559,1,'2020-01-01 01:01:01'),(498,20260319120000,1,'2020-01-01 01:01:01'),(499,20260323144117,1,'2020-01-01 01:01:01'),(500,20260324161944,1,'2020-01-01 01:01:01'),(501,20260324223334,1,'2020-01-01 01:01:01'),(502,20260326131501,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01'),(393,20250609112613,1,'2020-01-01 01:01:01'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260314120000,1,'2020-01-01 01:01:01'),(484,20260316120000,1,'2020-01-01 01:01:01'),(485,20260316120001,1,'2020-01-01 01:01:01'),(486,20260316120002,1,'2020-01-01 01:01:01'),(487,20260316120003,1,'2020-01-01 01:01:01'),(488,20260316120004,1,'2020-01-01 01:01:01'),(489,20260316120005,1,'2020-01-01 01:01:01'),(490,20260316120006,1,'2020-01-01 01:01:01'),(491,20260316120007,1,'2020-01-01 01:01:01'),(492,20260316120008,1,'2020-01-01 01:01:01'),(493,20260316120009,1,'2020-01-01 01:01:01'),(494,20260316120010,1,'2020-01-01 01:01:01'),(495,20260316120011,1,'2020-01-01 01:01:01'),(496,20260317120000,1,'2020-01-01 01:01:01'),(497,20260318184559,1,'2020-01-01 01:01:01'),(498,20260319120000,1,'2020-01-01 01:01:01'),(499,20260323144117,1,'2020-01-01 01:01:01'),(500,20260324161944,1,'2020-01-01 01:01:01'),(501,20260324223334,1,'2020-01-01 01:01:01'),(502,20260326131501,1,'2020-01-01 01:01:01'),(503,20260326210603,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( diff --git a/server/datastore/mysql/software.go b/server/datastore/mysql/software.go index 45541abc7f..582787dd46 100644 --- a/server/datastore/mysql/software.go +++ b/server/datastore/mysql/software.go @@ -925,6 +925,22 @@ func (ds *Datastore) preInsertSoftwareInventory( } } + // Fetch FMA canonical names to override osquery-reported names for macOS apps. + // This ensures software titles use consistent names (e.g., "Microsoft Visual Studio Code" + // instead of "Code" which is what osquery reports for VS Code). + // Note: This call is made from the base datastore so it bypasses the cached_mysql layer. + // The query is simple (SELECT from the small fleet_maintained_apps table) so this is acceptable. + // The cached_mysql layer still caches this method for other callers (e.g., API endpoints). + fmaNames, fmaErr := ds.GetFMANamesByIdentifier(ctx) + if fmaErr != nil { + // Log but don't fail - we can still use osquery-reported names. + // A nil map is safe here since Go's map access on nil returns the zero value. + if ds.logger != nil { + ds.logger.WarnContext(ctx, "failed to get FMA names by identifier", "err", fmaErr) + } + fmaNames = nil + } + // Process in smaller batches to reduce lock time err := common_mysql.BatchProcessSimple(keys, softwareInventoryInsertBatchSize, func(batchKeys []string) error { batchSoftware := make(map[string]fleet.Software, len(batchKeys)) @@ -941,13 +957,19 @@ func (ds *Datastore) preInsertSoftwareInventory( // there is not an existing software title corresponding to this incoming software version newTitleName := sw.Name if sw.BundleIdentifier != "" { - key := titleKey{ - bundleID: sw.BundleIdentifier, - source: sw.Source, - extensionFor: sw.ExtensionFor, - } - if computedName, exists := bestTitleNames[key]; exists { - newTitleName = computedName + // First check if there's an FMA with this bundle identifier - use its canonical name + if fmaName, ok := fmaNames[sw.BundleIdentifier]; ok { + newTitleName = fmaName + } else { + // Fall back to computed best name from osquery reports + key := titleKey{ + bundleID: sw.BundleIdentifier, + source: sw.Source, + extensionFor: sw.ExtensionFor, + } + if computedName, exists := bestTitleNames[key]; exists { + newTitleName = computedName + } } } @@ -1186,8 +1208,34 @@ func (ds *Datastore) preInsertSoftwareInventory( missingSoftwareTitles = append(missingSoftwareTitles, fmt.Sprintf("%s %s %s", sw.Name, sw.Version, sw.Source)) } + + // Use FMA canonical name if available, otherwise use osquery-reported name. + // This ensures software.name matches software_titles.name for consistency. + // + // IMPORTANT: The checksum is intentionally computed from osquery data + // (including the osquery-reported name, NOT the FMA name) for these reasons: + // + // 1. The checksum is used for deduplication via unique index. It serves as + // an internal identifier, not a content integrity hash. The stored name + // can differ from the name used in checksum computation. + // + // 2. Checksums are computed before FMA lookup, using raw osquery data. + // If we regenerated checksums with FMA names: + // - A cache miss or FMA sync delay could cause the same software to + // generate different checksums, creating duplicate entries. + // - Migration would require recomputing checksums for millions of rows. + // + // 3. The checksum is never recomputed from stored data - it's only computed + // from incoming osquery data during ingestion and used for lookup. + softwareName := sw.Name + if sw.BundleIdentifier != "" { + if fmaName, ok := fmaNames[sw.BundleIdentifier]; ok { + softwareName = fmaName + } + } + args = append( - args, sw.Name, sw.Version, sw.Source, sw.Release, sw.Vendor, sw.Arch, + args, softwareName, sw.Version, sw.Source, sw.Release, sw.Vendor, sw.Arch, sw.BundleIdentifier, sw.ExtensionID, sw.ExtensionFor, titleID, checksum, sw.ApplicationID, sw.UpgradeCode, ) } diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index c00924ad06..49ff7ee2b3 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -2447,6 +2447,11 @@ type Datastore interface { // metadata provided via app. UpsertMaintainedApp(ctx context.Context, app *MaintainedApp) (*MaintainedApp, error) + // GetFMANamesByIdentifier returns a map of unique_identifier -> canonical name + // for all Fleet-maintained apps on macOS. This is used during software ingestion + // to use the FMA name instead of the osquery-reported name. + GetFMANamesByIdentifier(ctx context.Context) (map[string]string, error) + // ///////////////////////////////////////////////////////////////////////////// // Certificate management diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index 3b94f62ea3..e5a2520f7a 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -1553,6 +1553,8 @@ type GetMaintainedAppBySlugFunc func(ctx context.Context, slug string, teamID *u type UpsertMaintainedAppFunc func(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) +type GetFMANamesByIdentifierFunc func(ctx context.Context) (map[string]string, error) + type BulkUpsertMDMManagedCertificatesFunc func(ctx context.Context, payload []*fleet.MDMManagedCertificate) error type GetAppleHostMDMCertificateProfileFunc func(ctx context.Context, hostUUID string, profileUUID string, caName string) (*fleet.HostMDMCertificateProfile, error) @@ -4133,6 +4135,9 @@ type DataStore struct { UpsertMaintainedAppFunc UpsertMaintainedAppFunc UpsertMaintainedAppFuncInvoked bool + GetFMANamesByIdentifierFunc GetFMANamesByIdentifierFunc + GetFMANamesByIdentifierFuncInvoked bool + BulkUpsertMDMManagedCertificatesFunc BulkUpsertMDMManagedCertificatesFunc BulkUpsertMDMManagedCertificatesFuncInvoked bool @@ -9917,6 +9922,13 @@ func (s *DataStore) UpsertMaintainedApp(ctx context.Context, app *fleet.Maintain return s.UpsertMaintainedAppFunc(ctx, app) } +func (s *DataStore) GetFMANamesByIdentifier(ctx context.Context) (map[string]string, error) { + s.mu.Lock() + s.GetFMANamesByIdentifierFuncInvoked = true + s.mu.Unlock() + return s.GetFMANamesByIdentifierFunc(ctx) +} + func (s *DataStore) BulkUpsertMDMManagedCertificates(ctx context.Context, payload []*fleet.MDMManagedCertificate) error { s.mu.Lock() s.BulkUpsertMDMManagedCertificatesFuncInvoked = true