diff --git a/changes/32751-installer-not-found b/changes/32751-installer-not-found new file mode 100644 index 0000000000..082c6af173 --- /dev/null +++ b/changes/32751-installer-not-found @@ -0,0 +1 @@ +* Fixed bug where uploading a software installer failed because it was "not found in the datastore". diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go index 6d83d29a08..719e000e03 100644 --- a/ee/server/service/software_installers.go +++ b/ee/server/service/software_installers.go @@ -19,6 +19,7 @@ import ( "github.com/fleetdm/fleet/v4/server" "github.com/fleetdm/fleet/v4/server/authz" authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz" + "github.com/fleetdm/fleet/v4/server/contexts/ctxdb" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" hostctx "github.com/fleetdm/fleet/v4/server/contexts/host" "github.com/fleetdm/fleet/v4/server/contexts/viewer" @@ -152,7 +153,7 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet. if payload.TeamID != nil { tmID = *payload.TeamID } - addedInstaller, err := svc.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &tmID, titleID, true) + addedInstaller, err := svc.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctxdb.RequirePrimary(ctx, true), &tmID, titleID, true) if err != nil { return nil, ctxerr.Wrap(ctx, err, "getting added software installer") } @@ -599,7 +600,7 @@ func (svc *Service) UpdateSoftwareInstaller(ctx context.Context, payload *fleet. } // re-pull installer from database to ensure any side effects are accounted for; may be able to optimize this out later - updatedInstaller, err := svc.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, payload.TeamID, payload.TitleID, true) + updatedInstaller, err := svc.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctxdb.RequirePrimary(ctx, true), payload.TeamID, payload.TitleID, true) if err != nil { return nil, ctxerr.Wrap(ctx, err, "re-hydrating updated installer metadata") } diff --git a/server/datastore/mysql/software_installers_test.go b/server/datastore/mysql/software_installers_test.go index 5586d3f456..ecaef800c4 100644 --- a/server/datastore/mysql/software_installers_test.go +++ b/server/datastore/mysql/software_installers_test.go @@ -12,8 +12,10 @@ import ( "testing" "time" + "github.com/fleetdm/fleet/v4/server/contexts/ctxdb" "github.com/fleetdm/fleet/v4/server/datastore/filesystem" "github.com/fleetdm/fleet/v4/server/datastore/mysql/common_mysql" + "github.com/fleetdm/fleet/v4/server/datastore/mysql/common_mysql/testing_utils" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/test" @@ -49,6 +51,7 @@ func TestSoftwareInstallers(t *testing.T) { {"EditDeleteSoftwareInstallersActivateNextActivity", testEditDeleteSoftwareInstallersActivateNextActivity}, {"BatchSetSoftwareInstallersActivateNextActivity", testBatchSetSoftwareInstallersActivateNextActivity}, {"SaveInstallerUpdatesClearsFleetMaintainedAppID", testSaveInstallerUpdatesClearsFleetMaintainedAppID}, + {"SoftwareInstallerReplicaLag", testSoftwareInstallerReplicaLag}, } for _, c := range cases { @@ -3186,3 +3189,40 @@ func testSaveInstallerUpdatesClearsFleetMaintainedAppID(t *testing.T, ds *Datast require.NoError(t, err) assert.Nil(t, fmaID, "fleet_maintained_app_id should be NULL after update") } + +func testSoftwareInstallerReplicaLag(t *testing.T, _ *Datastore) { + opts := &testing_utils.DatastoreTestOptions{DummyReplica: true} + ds := CreateMySQLDSWithOptions(t, opts) + defer ds.Close() + + ctx := context.Background() + test.NewHost(t, ds, "host1", "", "host1key", "host1uuid", time.Now()) + user := test.NewUser(t, ds, "Alice", "alice@example.com", true) + team, err := ds.NewTeam(ctx, &fleet.Team{Name: "Team 1"}) + require.NoError(t, err) + opts.RunReplication() + + // upload software installer + installerID, titleID, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{ + Title: "foo", + Source: "apps", + Version: "1.0", + InstallScript: "echo", + StorageID: "storage", + Filename: "installer.pkg", + BundleIdentifier: "com.foo.installer", + UserID: user.ID, + TeamID: &team.ID, + ValidatedLabels: &fleet.LabelIdentsWithScope{}, + }) + require.NoError(t, err) + require.NotZero(t, installerID) + require.NotZero(t, titleID) + // opts.RunReplication() // - replication should not be needed after fix + ctx = ctxdb.RequirePrimary(ctx, true) + + // then validate it GetSoftwareInstallerMetadataByTeamAndTitleID() + gotInstaller, err := ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, titleID, false) + require.NoError(t, err) + require.NotNil(t, gotInstaller) +} diff --git a/server/datastore/mysql/testing_utils.go b/server/datastore/mysql/testing_utils.go index 3cb49cb98b..66ea690dfe 100644 --- a/server/datastore/mysql/testing_utils.go +++ b/server/datastore/mysql/testing_utils.go @@ -197,11 +197,25 @@ func setupDummyReplica(t testing.TB, testName string, ds *Datastore, opts *testi t.Log(stmt) _, err = replica.ExecContext(ctx, stmt) require.NoError(t, err) + stmt = fmt.Sprintf(`CREATE TABLE %s.%s LIKE %s.%s`, replicaDB, tbl, testName, tbl) t.Log(stmt) _, err = replica.ExecContext(ctx, stmt) require.NoError(t, err) - stmt = fmt.Sprintf(`INSERT INTO %s.%s SELECT * FROM %s.%s`, replicaDB, tbl, testName, tbl) + + // Build query to avoid inserting into GENERATED columns + var columns string + columnsStmt := fmt.Sprintf(`SELECT + GROUP_CONCAT(column_name ORDER BY ordinal_position) + FROM information_schema.columns + WHERE table_schema = '%s' AND table_name = '%s' + AND NOT (EXTRA LIKE '%%GENERATED%%' AND EXTRA NOT LIKE '%%DEFAULT_GENERATED%%');`, replicaDB, tbl) + err = replica.GetContext(ctx, &columns, columnsStmt) + require.NoError(t, err) + + stmt = fmt.Sprintf(`INSERT INTO %s.%s (%s) + SELECT %s + FROM %s.%s;`, replicaDB, tbl, columns, columns, testName, tbl) t.Log(stmt) _, err = replica.ExecContext(ctx, stmt) require.NoError(t, err)