Propagate self-service flag on uninstalls through to activity (#29691)
Fixes part of unreleased for #28846. # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated automated tests - [ ] Manual QA for all new/changed functionality
This commit is contained in:
@@ -396,6 +396,7 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
|
||||
'software_title', COALESCE(st.name, ua.payload->>'$.software_title_name', ''),
|
||||
'script_execution_id', ua.execution_id,
|
||||
'status', 'pending_uninstall',
|
||||
'self_service', COALESCE(ua.payload->'$.self_service', FALSE) IS TRUE,
|
||||
'policy_id', siua.policy_id,
|
||||
'policy_name', p.name
|
||||
) as details,
|
||||
@@ -1112,7 +1113,7 @@ func (ds *Datastore) activateNextUpcomingActivityForBatchOfHosts(ctx context.Con
|
||||
// - If no other activity is still activated and there is an upcoming
|
||||
// activity to activate next, it does so, respecting the priority and enqueue
|
||||
// order. Activation consists of inserting the activity in its respective
|
||||
// table, e.g. `host_script_results` for scripts, `host_sofware_installs` for
|
||||
// table, e.g. `host_script_results` for scripts, `host_software_installs` for
|
||||
// software installs, `host_vpp_software_installs` and nano command queue for
|
||||
// VPP installs; and setting the activated_at timestamp in the
|
||||
// `upcoming_activities` table.
|
||||
@@ -1359,7 +1360,7 @@ ORDER BY
|
||||
INSERT INTO
|
||||
host_software_installs
|
||||
(execution_id, host_id, software_installer_id, user_id, uninstall, installer_filename,
|
||||
software_title_id, software_title_name, version)
|
||||
software_title_id, software_title_name, self_service, version)
|
||||
SELECT
|
||||
ua.execution_id,
|
||||
ua.host_id,
|
||||
@@ -1369,6 +1370,7 @@ SELECT
|
||||
'', -- no installer_filename for uninstalls
|
||||
siua.software_title_id,
|
||||
COALESCE(ua.payload->>'$.software_title_name', '[deleted title]'),
|
||||
COALESCE(ua.payload->>'$.self_service', FALSE),
|
||||
'unknown'
|
||||
FROM
|
||||
upcoming_activities ua
|
||||
|
||||
@@ -571,7 +571,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
|
||||
// create pending install and uninstall requests for h3 that will be deleted
|
||||
_, err = ds.InsertSoftwareInstallRequest(ctx, h3.ID, sw3Meta.InstallerID, fleet.HostSoftwareInstallOptions{})
|
||||
require.NoError(t, err)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "uninstallRun", h3.ID, sw3Meta.InstallerID)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "uninstallRun", h3.ID, sw3Meta.InstallerID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// delete installer (should clear pending requests)
|
||||
@@ -1344,7 +1344,7 @@ func testActivateNextActivity(t *testing.T, ds *Datastore) {
|
||||
|
||||
// create a pending uninstall request
|
||||
sw1_2 := uuid.NewString()
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, sw1_2, h1.ID, sw1)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, sw1_2, h1.ID, sw1, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// still hasn't changed the pending queue
|
||||
@@ -1495,7 +1495,7 @@ func testActivateItselfOnEmptyQueue(t *testing.T, ds *Datastore) {
|
||||
|
||||
// create a pending uninstall request
|
||||
sw1_2 := uuid.NewString()
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, sw1_2, h1.ID, sw1)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, sw1_2, h1.ID, sw1, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// set a result for the software uninstall
|
||||
|
||||
@@ -1060,7 +1060,7 @@ func (ds *Datastore) runInstallerUpdateSideEffectsInTransaction(ctx context.Cont
|
||||
return affectedHostIDs, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) InsertSoftwareUninstallRequest(ctx context.Context, executionID string, hostID uint, softwareInstallerID uint) error {
|
||||
func (ds *Datastore) InsertSoftwareUninstallRequest(ctx context.Context, executionID string, hostID uint, softwareInstallerID uint, selfService bool) error {
|
||||
const (
|
||||
getInstallerStmt = `SELECT title_id, COALESCE(st.name, '[deleted title]') title_name
|
||||
FROM software_installers si LEFT JOIN software_titles st ON si.title_id = st.id WHERE si.id = ?`
|
||||
@@ -1074,7 +1074,8 @@ VALUES
|
||||
'installer_filename', '',
|
||||
'version', 'unknown',
|
||||
'software_title_name', ?,
|
||||
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = ?)
|
||||
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = ?),
|
||||
'self_service', ?
|
||||
)
|
||||
)`
|
||||
|
||||
@@ -1123,6 +1124,7 @@ VALUES
|
||||
executionID,
|
||||
installerDetails.TitleName,
|
||||
userID,
|
||||
selfService,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2305,17 +2307,17 @@ func (ds *Datastore) HasSelfServiceSoftwareInstallers(ctx context.Context, hostP
|
||||
return hasInstallers, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetSoftwareTitleNameFromExecutionID(ctx context.Context, executionID string) (string, error) {
|
||||
func (ds *Datastore) GetDetailsForUninstallFromExecutionID(ctx context.Context, executionID string) (string, bool, error) {
|
||||
stmt := `
|
||||
SELECT st.name
|
||||
SELECT COALESCE(st.name, hsi.software_title_name) name, hsi.self_service
|
||||
FROM software_titles st
|
||||
INNER JOIN software_installers si ON si.title_id = st.id
|
||||
INNER JOIN host_software_installs hsi ON hsi.software_installer_id = si.id
|
||||
WHERE hsi.execution_id = ?
|
||||
WHERE hsi.execution_id = ? AND hsi.uninstall = TRUE
|
||||
|
||||
UNION
|
||||
|
||||
SELECT st.name
|
||||
SELECT st.name, COALESCE(ua.payload->'$.self_service', FALSE) self_service
|
||||
FROM
|
||||
software_titles st
|
||||
INNER JOIN software_installers si ON si.title_id = st.id
|
||||
@@ -2324,14 +2326,17 @@ func (ds *Datastore) GetSoftwareTitleNameFromExecutionID(ctx context.Context, ex
|
||||
INNER JOIN upcoming_activities ua ON ua.id = siua.upcoming_activity_id
|
||||
WHERE
|
||||
ua.execution_id = ? AND
|
||||
ua.activity_type IN ('software_install', 'software_uninstall')
|
||||
ua.activity_type = 'software_uninstall'
|
||||
`
|
||||
var name string
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &name, stmt, executionID, executionID)
|
||||
if err != nil {
|
||||
return "", ctxerr.Wrap(ctx, err, "get software title name from execution ID")
|
||||
var result struct {
|
||||
Name string `db:"name"`
|
||||
SelfService bool `db:"self_service"`
|
||||
}
|
||||
return name, nil
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &result, stmt, executionID, executionID)
|
||||
if err != nil {
|
||||
return "", false, ctxerr.Wrap(ctx, err, "get software details for uninstall activity from execution ID")
|
||||
}
|
||||
return result.Name, result.SelfService, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetSoftwareInstallersWithoutPackageIDs(ctx context.Context) (map[uint]string, error) {
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestSoftwareInstallers(t *testing.T) {
|
||||
{"GetOrGenerateSoftwareInstallerTitleID", testGetOrGenerateSoftwareInstallerTitleID},
|
||||
{"BatchSetSoftwareInstallersScopedViaLabels", testBatchSetSoftwareInstallersScopedViaLabels},
|
||||
{"MatchOrCreateSoftwareInstallerWithAutomaticPolicies", testMatchOrCreateSoftwareInstallerWithAutomaticPolicies},
|
||||
{"GetSoftwareTitleNameFromExecutionID", testGetSoftwareTitleNameFromExecutionID},
|
||||
{"GetDetailsForUninstallFromExecutionID", testGetDetailsForUninstallFromExecutionID},
|
||||
{"GetTeamsWithInstallerByHash", testGetTeamsWithInstallerByHash},
|
||||
{"BatchSetSoftwareInstallersSetupExperienceSideEffects", testBatchSetSoftwareInstallersSetupExperienceSideEffects},
|
||||
{"EditDeleteSoftwareInstallersActivateNextActivity", testEditDeleteSoftwareInstallersActivateNextActivity},
|
||||
@@ -372,7 +372,7 @@ func testSoftwareInstallRequests(t *testing.T, ds *Datastore) {
|
||||
TeamID: teamID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "uuid"+tag+tc, hostPendingUninstall.ID, si.InstallerID)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "uuid"+tag+tc, hostPendingUninstall.ID, si.InstallerID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Host with failed uninstall
|
||||
@@ -387,7 +387,7 @@ func testSoftwareInstallRequests(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
execID = "uuid" + tag + tc
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, execID, hostFailedUninstall.ID, si.InstallerID)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, execID, hostFailedUninstall.ID, si.InstallerID, false)
|
||||
require.NoError(t, err)
|
||||
_, _, err = ds.SetHostScriptExecutionResult(ctx, &fleet.HostScriptResultPayload{
|
||||
HostID: hostFailedUninstall.ID,
|
||||
@@ -408,7 +408,7 @@ func testSoftwareInstallRequests(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
execID = "uuid" + tag + tc
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, execID, hostUninstalled.ID, si.InstallerID)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, execID, hostUninstalled.ID, si.InstallerID, false)
|
||||
require.NoError(t, err)
|
||||
_, _, err = ds.SetHostScriptExecutionResult(ctx, &fleet.HostScriptResultPayload{
|
||||
HostID: hostUninstalled.ID,
|
||||
@@ -418,7 +418,7 @@ func testSoftwareInstallRequests(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Uninstall request with unknown host
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "uuid"+tag+tc, 99999, si.InstallerID)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "uuid"+tag+tc, 99999, si.InstallerID, false)
|
||||
assert.ErrorContains(t, err, "Host")
|
||||
|
||||
userTeamFilter := fleet.TeamFilter{
|
||||
@@ -2576,7 +2576,7 @@ Software won't be installed on Linux hosts with Debian-based distributions becau
|
||||
require.Equal(t, "[Install software] Something2 (msi) 3", team3Policies[2].Name)
|
||||
}
|
||||
|
||||
func testGetSoftwareTitleNameFromExecutionID(t *testing.T, ds *Datastore) {
|
||||
func testGetDetailsForUninstallFromExecutionID(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
user := test.NewUser(t, ds, "Alice", "alice@example.com", true)
|
||||
@@ -2615,9 +2615,10 @@ func testGetSoftwareTitleNameFromExecutionID(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// get software title for unknown exec id
|
||||
title, err := ds.GetSoftwareTitleNameFromExecutionID(ctx, "unknown")
|
||||
title, selfService, err := ds.GetDetailsForUninstallFromExecutionID(ctx, "unknown")
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
require.Empty(t, title)
|
||||
require.False(t, selfService)
|
||||
|
||||
// create a couple pending software install request, the first will be
|
||||
// immediately present in host_software_installs too (activated)
|
||||
@@ -2626,13 +2627,8 @@ func testGetSoftwareTitleNameFromExecutionID(t *testing.T, ds *Datastore) {
|
||||
req2, err := ds.InsertSoftwareInstallRequest(ctx, host.ID, installer2, fleet.HostSoftwareInstallOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
title, err = ds.GetSoftwareTitleNameFromExecutionID(ctx, req1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", title)
|
||||
|
||||
title, err = ds.GetSoftwareTitleNameFromExecutionID(ctx, req2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "barfoo", title)
|
||||
_, _, err = ds.GetDetailsForUninstallFromExecutionID(ctx, req1)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
|
||||
// record a result for req1, will be deleted from upcoming_activities
|
||||
_, err = ds.SetHostSoftwareInstallResult(ctx, &fleet.HostSoftwareInstallResultPayload{
|
||||
@@ -2642,22 +2638,18 @@ func testGetSoftwareTitleNameFromExecutionID(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
title, err = ds.GetSoftwareTitleNameFromExecutionID(ctx, req1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", title)
|
||||
|
||||
title, err = ds.GetSoftwareTitleNameFromExecutionID(ctx, req2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "barfoo", title)
|
||||
_, _, err = ds.GetDetailsForUninstallFromExecutionID(ctx, req1)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
|
||||
// create an uninstall request for installer1
|
||||
req3 := uuid.NewString()
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, req3, host.ID, installer1)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, req3, host.ID, installer1, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
title, err = ds.GetSoftwareTitleNameFromExecutionID(ctx, req3)
|
||||
title, selfService, err = ds.GetDetailsForUninstallFromExecutionID(ctx, req3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", title)
|
||||
require.True(t, selfService)
|
||||
|
||||
// record a result for req2, will activate req3 so it is now in host_software_installs too
|
||||
_, err = ds.SetHostSoftwareInstallResult(ctx, &fleet.HostSoftwareInstallResultPayload{
|
||||
@@ -2667,9 +2659,10 @@ func testGetSoftwareTitleNameFromExecutionID(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
title, err = ds.GetSoftwareTitleNameFromExecutionID(ctx, req3)
|
||||
title, selfService, err = ds.GetDetailsForUninstallFromExecutionID(ctx, req3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", title)
|
||||
require.True(t, selfService)
|
||||
}
|
||||
|
||||
func testGetTeamsWithInstallerByHash(t *testing.T, ds *Datastore) {
|
||||
|
||||
@@ -3919,14 +3919,14 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
return err
|
||||
}
|
||||
hostSwi6UninstallUUID = uuid.NewString()
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, hostSwi6UninstallUUID, host.ID, swi6PendingUninstall)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, hostSwi6UninstallUUID, host.ID, swi6PendingUninstall, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// swi7 is failed uninstall
|
||||
hostSwi7UninstallUUID = uuid.NewString()
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, hostSwi7UninstallUUID, host.ID, swi7FailedUninstall)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, hostSwi7UninstallUUID, host.ID, swi7FailedUninstall, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -3948,7 +3948,7 @@ func testListHostSoftware(t *testing.T, ds *Datastore) {
|
||||
|
||||
// swi8 is successful uninstall
|
||||
hostSwi8UninstallUUID = uuid.NewString()
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, hostSwi8UninstallUUID, host.ID, swi8Uninstalled)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, hostSwi8UninstallUUID, host.ID, swi8Uninstalled, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -6815,7 +6815,7 @@ func testListHostSoftwareVulnerabileAndVPP(t *testing.T, ds *Datastore) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
// pending install request
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "abc123", tmHost.ID, installerID)
|
||||
err = ds.InsertSoftwareUninstallRequest(ctx, "abc123", tmHost.ID, installerID, true)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
|
||||
// Ensure that software "a" & "b" are returned as they are the only vulnerable apps at this point
|
||||
|
||||
Reference in New Issue
Block a user