CP: Fix activity logs for failed script package installations (#35124)

Cherry-pick of #34951
This commit is contained in:
Carlo 2025-11-04 08:24:34 -05:00 committed by GitHub
parent b3b667a40c
commit bf3800b862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 256 additions and 6 deletions

View file

@ -1460,14 +1460,18 @@ SELECT
ua.user_id,
COALESCE(ua.payload->'$.self_service', 0),
siua.policy_id,
COALESCE(ua.payload->>'$.installer_filename', '[deleted installer]'),
COALESCE(ua.payload->>'$.version', 'unknown'),
siua.software_title_id,
COALESCE(ua.payload->>'$.software_title_name', '[deleted title]')
COALESCE(si.filename, ua.payload->>'$.installer_filename', '[deleted installer]'),
COALESCE(si.version, ua.payload->>'$.version', 'unknown'),
COALESCE(si.title_id, siua.software_title_id),
COALESCE(st.name, ua.payload->>'$.software_title_name', '[deleted title]')
FROM
upcoming_activities ua
INNER JOIN software_install_upcoming_activities siua
ON siua.upcoming_activity_id = ua.id
LEFT JOIN software_installers si
ON si.id = siua.software_installer_id
LEFT JOIN software_titles st
ON st.id = si.title_id
WHERE
ua.host_id = ? AND
ua.execution_id IN (?)
@ -1526,14 +1530,18 @@ SELECT
ua.user_id,
1, -- uninstall
'', -- no installer_filename for uninstalls
siua.software_title_id,
COALESCE(ua.payload->>'$.software_title_name', '[deleted title]'),
COALESCE(si.title_id, siua.software_title_id),
COALESCE(st.name, ua.payload->>'$.software_title_name', '[deleted title]'),
COALESCE(ua.payload->>'$.self_service', FALSE),
'unknown'
FROM
upcoming_activities ua
INNER JOIN software_install_upcoming_activities siua
ON siua.upcoming_activity_id = ua.id
LEFT JOIN software_installers si
ON si.id = siua.software_installer_id
LEFT JOIN software_titles st
ON st.id = si.title_id
WHERE
ua.host_id = ? AND
ua.execution_id IN (?)

View file

@ -46,6 +46,10 @@ func TestActivity(t *testing.T) {
{"SetResultAfterCancelUpcomingActivity", testSetResultAfterCancelUpcomingActivity},
{"GetHostUpcomingActivityMeta", testGetHostUpcomingActivityMeta},
{"UnblockHostsUpcomingActivityQueue", testUnblockHostsUpcomingActivityQueue},
{"ActivateScriptPackageInstallWithCorruptPayload", testActivateScriptPackageInstallWithCorruptPayload},
{"ActivateRegularPackageInstall", testActivateRegularPackageInstall},
{"ActivateDeletedInstallerShowsPlaceholder", testActivateDeletedInstallerShowsPlaceholder},
{"ActivateScriptPackageUninstallWithCorruptPayload", testActivateScriptPackageUninstallWithCorruptPayload},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@ -2390,3 +2394,241 @@ func testUnblockHostsUpcomingActivityQueue(t *testing.T, ds *Datastore) {
checkUpcomingActivities(t, ds, hosts[3], host3ScriptE.ExecutionID)
checkUpcomingActivities(t, ds, hosts[4], host4ScriptE.ExecutionID)
}
func testActivateScriptPackageInstallWithCorruptPayload(t *testing.T, ds *Datastore) {
ctx := context.Background()
host := test.NewHost(t, ds, "host1", "192.168.1.1", "1", "1", time.Now())
titleStmt := `INSERT INTO software_titles (name, source, extension_for) VALUES (?, ?, '')`
res, err := ds.writer(ctx).ExecContext(ctx, titleStmt, "Test Script", "sh_packages")
require.NoError(t, err)
titleID, _ := res.LastInsertId()
u := test.NewUser(t, ds, "user1", "user1@example.com", false)
scriptContentStmt := `INSERT INTO script_contents (md5_checksum, contents) VALUES (?, ?)`
res, err = ds.writer(ctx).ExecContext(ctx, scriptContentStmt, "abc123", "#!/bin/bash\necho 'test'")
require.NoError(t, err)
scriptContentID, _ := res.LastInsertId()
installerStmt := `
INSERT INTO software_installers (
team_id, global_or_team_id, title_id, storage_id, filename,
extension, version, platform, install_script_content_id,
pre_install_query, post_install_script_content_id, uninstall_script_content_id,
self_service, user_id, user_name, user_email, package_ids,
fleet_maintained_app_id, url, upgrade_code
)
VALUES (NULL, 0, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
`
res, err = ds.writer(ctx).ExecContext(ctx, installerStmt,
titleID, "storage-123", "test-script.sh", "sh", "", "linux", scriptContentID,
"", scriptContentID, 0, u.ID, u.Name, u.Email, "", "", "")
require.NoError(t, err)
installerID, _ := res.LastInsertId()
execID := uuid.NewString()
uaStmt := `INSERT INTO upcoming_activities (host_id, priority, activity_type, execution_id, payload) VALUES (?, 1, 'software_install', ?, JSON_OBJECT())`
res, err = ds.writer(ctx).ExecContext(ctx, uaStmt, host.ID, execID)
require.NoError(t, err)
activityID, _ := res.LastInsertId()
siuaStmt := `INSERT INTO software_install_upcoming_activities (upcoming_activity_id, software_installer_id, policy_id, software_title_id) VALUES (?, ?, NULL, NULL)`
_, err = ds.writer(ctx).ExecContext(ctx, siuaStmt, activityID, installerID)
require.NoError(t, err)
err = ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
_, err := ds.activateNextUpcomingActivity(ctx, tx, host.ID, "")
return err
})
require.NoError(t, err)
var result struct {
SoftwareTitleID *uint `db:"software_title_id"`
SoftwareTitleName string `db:"software_title_name"`
InstallerFilename string `db:"installer_filename"`
Version string `db:"version"`
}
err = ds.writer(ctx).GetContext(ctx, &result,
"SELECT software_title_id, software_title_name, installer_filename, version FROM host_software_installs WHERE execution_id = ?",
execID)
require.NoError(t, err)
require.NotNil(t, result.SoftwareTitleID)
require.Equal(t, uint(titleID), *result.SoftwareTitleID) //nolint:gosec // dismiss G115
require.Equal(t, "Test Script", result.SoftwareTitleName)
require.Equal(t, "test-script.sh", result.InstallerFilename)
require.Equal(t, "", result.Version)
}
func testActivateRegularPackageInstall(t *testing.T, ds *Datastore) {
ctx := context.Background()
host := test.NewHost(t, ds, "host2", "192.168.1.2", "2", "2", time.Now())
u := test.NewUser(t, ds, "user2", "user2@example.com", false)
installer, err := fleet.NewTempFileReader(strings.NewReader("fake pkg"), t.TempDir)
require.NoError(t, err)
installerID, titleID, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
InstallScript: "install regular",
InstallerFile: installer,
StorageID: uuid.NewString(),
Filename: "regular.pkg",
Title: "Regular Package",
Source: "pkg_packages",
Version: "1.0.0",
UserID: u.ID,
Extension: "pkg",
Platform: "darwin",
BundleIdentifier: "com.regular.pkg",
UninstallScript: "uninstall regular",
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
execID, err := ds.InsertSoftwareInstallRequest(ctx, host.ID, installerID, fleet.HostSoftwareInstallOptions{})
require.NoError(t, err)
var result struct {
SoftwareTitleID *uint `db:"software_title_id"`
SoftwareTitleName string `db:"software_title_name"`
InstallerFilename string `db:"installer_filename"`
Version string `db:"version"`
}
err = ds.writer(ctx).GetContext(ctx, &result,
"SELECT software_title_id, software_title_name, installer_filename, version FROM host_software_installs WHERE execution_id = ?",
execID)
require.NoError(t, err)
require.NotNil(t, result.SoftwareTitleID)
require.Equal(t, titleID, *result.SoftwareTitleID)
require.Equal(t, "Regular Package", result.SoftwareTitleName)
require.Equal(t, "regular.pkg", result.InstallerFilename)
require.Equal(t, "1.0.0", result.Version)
}
func testActivateDeletedInstallerShowsPlaceholder(t *testing.T, ds *Datastore) {
ctx := context.Background()
host := test.NewHost(t, ds, "host3", "192.168.1.3", "3", "3", time.Now())
u := test.NewUser(t, ds, "user3", "user3@example.com", false)
installer, err := fleet.NewTempFileReader(strings.NewReader("temp"), t.TempDir)
require.NoError(t, err)
installerID, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
InstallScript: "install temp",
InstallerFile: installer,
StorageID: uuid.NewString(),
Filename: "temp.pkg",
Title: "Temp Package",
Source: "pkg_packages",
Version: "1.0.0",
UserID: u.ID,
Extension: "pkg",
Platform: "darwin",
UninstallScript: "uninstall temp",
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
execID := uuid.NewString()
uaStmt := `INSERT INTO upcoming_activities (host_id, priority, activity_type, execution_id, payload) VALUES (?, 1, 'software_install', ?, JSON_OBJECT())`
res, err := ds.writer(ctx).ExecContext(ctx, uaStmt, host.ID, execID)
require.NoError(t, err)
activityID, _ := res.LastInsertId()
siuaStmt := `INSERT INTO software_install_upcoming_activities (upcoming_activity_id, software_installer_id, policy_id, software_title_id) VALUES (?, ?, NULL, NULL)`
_, err = ds.writer(ctx).ExecContext(ctx, siuaStmt, activityID, installerID)
require.NoError(t, err)
deleteStmt := `DELETE FROM software_installers WHERE id = ?`
_, err = ds.writer(ctx).ExecContext(ctx, deleteStmt, installerID)
require.NoError(t, err)
err = ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
_, err := ds.activateNextUpcomingActivity(ctx, tx, host.ID, "")
return err
})
require.NoError(t, err)
var result struct {
SoftwareTitleID *uint `db:"software_title_id"`
SoftwareTitleName string `db:"software_title_name"`
InstallerFilename string `db:"installer_filename"`
Version string `db:"version"`
}
err = ds.writer(ctx).GetContext(ctx, &result,
"SELECT software_title_id, software_title_name, installer_filename, version FROM host_software_installs WHERE execution_id = ?",
execID)
require.NoError(t, err)
require.Nil(t, result.SoftwareTitleID)
require.Equal(t, "[deleted title]", result.SoftwareTitleName)
require.Equal(t, "[deleted installer]", result.InstallerFilename)
require.Equal(t, "unknown", result.Version)
}
func testActivateScriptPackageUninstallWithCorruptPayload(t *testing.T, ds *Datastore) {
ctx := context.Background()
titleStmt := `INSERT INTO software_titles (name, source, extension_for) VALUES ('Test Uninstall Script', 'apps', '')`
res, err := ds.writer(ctx).ExecContext(ctx, titleStmt)
require.NoError(t, err)
titleID, _ := res.LastInsertId()
u := test.NewUser(t, ds, "uninstall-user", "uninstall@example.com", false)
scriptStmt := `INSERT INTO script_contents (md5_checksum, contents) VALUES (UNHEX(MD5('echo uninstalling')), 'echo uninstalling')`
res, err = ds.writer(ctx).ExecContext(ctx, scriptStmt)
require.NoError(t, err)
scriptContentID, _ := res.LastInsertId()
installerStmt := `
INSERT INTO software_installers (
team_id, global_or_team_id, title_id, storage_id, filename,
extension, version, platform, install_script_content_id,
pre_install_query, post_install_script_content_id, uninstall_script_content_id,
self_service, user_id, user_name, user_email, package_ids,
fleet_maintained_app_id, url, upgrade_code
)
VALUES (NULL, 0, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
`
res, err = ds.writer(ctx).ExecContext(ctx, installerStmt,
titleID, "storage-id-uninstall", "test-uninstall.sh", "sh", "", "linux", scriptContentID,
"", scriptContentID, 0, u.ID, u.Name, u.Email, "", "", "")
require.NoError(t, err)
installerID, _ := res.LastInsertId()
host := test.NewHost(t, ds, "test-host", "", "test-key", "test-uuid", time.Now())
execID := "uninstall-exec-123"
uaStmt := `INSERT INTO upcoming_activities (host_id, activity_type, execution_id, user_id, payload, priority) VALUES (?, 'software_uninstall', ?, NULL, JSON_OBJECT(), 0)`
res, err = ds.writer(ctx).ExecContext(ctx, uaStmt, host.ID, execID)
require.NoError(t, err)
activityID, _ := res.LastInsertId()
siuaStmt := `INSERT INTO software_install_upcoming_activities (upcoming_activity_id, software_installer_id, software_title_id) VALUES (?, ?, NULL)`
_, err = ds.writer(ctx).ExecContext(ctx, siuaStmt, activityID, installerID)
require.NoError(t, err)
err = ds.activateNextSoftwareUninstallActivity(ctx, ds.writer(ctx), host.ID, []string{execID})
require.NoError(t, err)
var result struct {
SoftwareTitleID *uint `db:"software_title_id"`
SoftwareTitleName string `db:"software_title_name"`
Uninstall bool `db:"uninstall"`
}
err = sqlx.GetContext(ctx, ds.reader(ctx), &result,
"SELECT software_title_id, software_title_name, uninstall FROM host_software_installs WHERE execution_id = ?",
execID)
require.NoError(t, err)
require.True(t, result.Uninstall)
require.NotNil(t, result.SoftwareTitleID)
require.Equal(t, uint(titleID), *result.SoftwareTitleID) //nolint:gosec // dismiss G115
require.Equal(t, "Test Uninstall Script", result.SoftwareTitleName)
}