diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go index b7faa26d8c..ff645c7321 100644 --- a/server/datastore/mysql/software_installers.go +++ b/server/datastore/mysql/software_installers.go @@ -3941,13 +3941,13 @@ func (ds *Datastore) GetSoftwareTitlesForInstallAll(ctx context.Context, host *f } } - // filter out pending or already installed software, and software not in the category if one is provided + // filter out pending or already installed software, and software not in the category if one is provided. + // Failed install/uninstall states are included so they get re-queued (matches the per-row Retry behavior). var toInstall []*fleet.HostSoftwareWithInstaller for _, s := range software { if s.Status != nil { switch *s.Status { - case fleet.SoftwareInstallPending, fleet.SoftwareUninstallPending, fleet.SoftwareInstalled, - fleet.SoftwareInstallFailed, fleet.SoftwareUninstallFailed: + case fleet.SoftwareInstallPending, fleet.SoftwareUninstallPending, fleet.SoftwareInstalled: continue } } diff --git a/server/datastore/mysql/software_installers_test.go b/server/datastore/mysql/software_installers_test.go index cecf31fa47..bf9a757a2f 100644 --- a/server/datastore/mysql/software_installers_test.go +++ b/server/datastore/mysql/software_installers_test.go @@ -5624,7 +5624,8 @@ func testGetSoftwareTitlesForInstallAll(t *testing.T, ds *Datastore) { ByName: map[string]fleet.LabelIdent{lbl.Name: {LabelID: lbl.ID, LabelName: lbl.Name}}, }) - // skipped for various reasons + // previously installed/pending titles are skipped; failed_install and failed_uninstall + // are included so install_all re-queues them (matches per-row Retry). installedID, _ := newInstaller("installed", true, nil, nil, noLabels) installedUpdateID, _ := newInstaller("installed-update", true, nil, nil, noLabels) failedID, _ := newInstaller("failed", true, nil, nil, noLabels) @@ -5658,11 +5659,12 @@ func testGetSoftwareTitlesForInstallAll(t *testing.T, ds *Datastore) { _, err = ds.InsertSoftwareInstallRequest(ctx, host.ID, pendingID, fleet.HostSoftwareInstallOptions{SelfService: true}) require.NoError(t, err) - // no category: only the available titles, returned in alphabetical order by name + // no category: only the available titles, returned in alphabetical order by name. + // failed_install and failed_uninstall are included so install_all re-queues them. got, categoryName, err := ds.GetSoftwareTitlesForInstallAll(ctx, host, nil) require.NoError(t, err) require.Nil(t, categoryName) - require.Equal(t, []string{"available", "label-in", "uninstalled"}, names(got)) + require.Equal(t, []string{"available", "failed", "failed-uninstall", "label-in", "uninstalled"}, names(got)) // scoped to a category: only the in-category title, and the name is returned got, categoryName, err = ds.GetSoftwareTitlesForInstallAll(ctx, host, &cat.ID)