diff --git a/server/datastore/mysql/vpp.go b/server/datastore/mysql/vpp.go index 88fc13ae27..614557559c 100644 --- a/server/datastore/mysql/vpp.go +++ b/server/datastore/mysql/vpp.go @@ -141,9 +141,7 @@ func (ds *Datastore) BatchInsertVPPApps(ctx context.Context, apps []*fleet.VPPAp func (ds *Datastore) InsertVPPAppWithTeam(ctx context.Context, app *fleet.VPPApp, teamID *uint) error { return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { - // TODO: we should not just create a software title, I think we should try - // to match to an existing one (i.e. getOrCreate logic)? - titleID, err := insertSoftwareTitleForVPPApp(ctx, tx, app) + titleID, err := ds.getOrInsertSoftwareTitleForVPPApp(ctx, tx, app) if err != nil { return err } @@ -234,17 +232,40 @@ VALUES return ctxerr.Wrap(ctx, err, "writing vpp app team mapping to db") } -func insertSoftwareTitleForVPPApp(ctx context.Context, tx sqlx.ExtContext, app *fleet.VPPApp) (uint, error) { - stmt := `INSERT INTO software_titles (name, source, bundle_identifier, browser) VALUES (?, '', ?, '')` +func (ds *Datastore) getOrInsertSoftwareTitleForVPPApp(ctx context.Context, tx sqlx.ExtContext, app *fleet.VPPApp) (uint, error) { + // NOTE: it was decided to leave the source empty for VPP apps for now, TBD + // if this needs to change to better map to how software titles are reported + // back by osquery. Since I think this will likely not stay empty, I'm using + // a variable for the source. + const source = "" - result, err := tx.ExecContext(ctx, stmt, app.Name, app.BundleIdentifier) - if err != nil { - return 0, ctxerr.Wrap(ctx, err, "writing vpp app software title") + selectStmt := `SELECT id FROM software_titles WHERE name = ? AND source = ? AND browser = ''` + selectArgs := []any{app.Name, source} + insertStmt := `INSERT INTO software_titles (name, source, browser) VALUES (?, ?, '')` + insertArgs := []any{app.Name, source} + + if app.BundleIdentifier != "" { + selectStmt = `SELECT id FROM software_titles WHERE bundle_identifier = ?` + selectArgs = []any{app.BundleIdentifier} + insertStmt = `INSERT INTO software_titles (name, source, bundle_identifier, browser) VALUES (?, ?, ?, '')` + insertArgs = append(insertArgs, app.BundleIdentifier) } - id, _ := result.LastInsertId() + titleID, err := ds.optimisticGetOrInsert(ctx, + ¶meterizedStmt{ + Statement: selectStmt, + Args: selectArgs, + }, + ¶meterizedStmt{ + Statement: insertStmt, + Args: insertArgs, + }, + ) + if err != nil { + return 0, err + } - return uint(id), nil + return titleID, nil } func (ds *Datastore) DeleteVPPAppFromTeam(ctx context.Context, teamID *uint, adamID string) error { diff --git a/server/datastore/mysql/vpp_test.go b/server/datastore/mysql/vpp_test.go index 9a13374ded..14b077d5ba 100644 --- a/server/datastore/mysql/vpp_test.go +++ b/server/datastore/mysql/vpp_test.go @@ -300,7 +300,27 @@ func testVPPApps(t *testing.T, ds *Datastore) { team, err := ds.NewTeam(ctx, &fleet.Team{Name: "foobar"}) require.NoError(t, err) - // Insert some VPP apps for the team + // create a host with some non-VPP software + h1, err := ds.NewHost(ctx, &fleet.Host{ + Hostname: "macos-test-1", + OsqueryHostID: ptr.String("osquery-macos-1"), + NodeKey: ptr.String("node-key-macos-1"), + UUID: uuid.NewString(), + Platform: "darwin", + HardwareSerial: "654321a", + }) + require.NoError(t, err) + software := []fleet.Software{ + {Name: "foo", Version: "0.0.1", BundleIdentifier: "b1"}, + {Name: "foo", Version: "0.0.2", BundleIdentifier: "b1"}, + {Name: "bar", Version: "0.0.3", BundleIdentifier: "bar"}, + } + _, err = ds.UpdateHostSoftware(ctx, h1.ID, software) + require.NoError(t, err) + err = ds.ReconcileSoftwareTitles(ctx) + require.NoError(t, err) + + // Insert some VPP apps for the team, "vpp_app_1" should match the existing "foo" title app1 := &fleet.VPPApp{Name: "vpp_app_1", AdamID: "1", BundleIdentifier: "b1"} app2 := &fleet.VPPApp{Name: "vpp_app_2", AdamID: "2", BundleIdentifier: "b2"} err = ds.InsertVPPAppWithTeam(ctx, app1, &team.ID) @@ -332,6 +352,6 @@ func testVPPApps(t *testing.T, ds *Datastore) { require.Len(t, appTitles, 2) require.Equal(t, app1.BundleIdentifier, *appTitles[0].BundleIdentifier) require.Equal(t, app2.BundleIdentifier, *appTitles[1].BundleIdentifier) - require.Equal(t, app1.Name, appTitles[0].Name) + require.Equal(t, "foo", appTitles[0].Name) require.Equal(t, app2.Name, appTitles[1].Name) }