Feature: Script only package e2e followup (#34271)
Co-authored-by: Carlo DiCelico <carlo@fleetdm.com>
This commit is contained in:
co-authored by
Carlo DiCelico
parent
927fd1d240
commit
1ef91fe4e3
@@ -1343,6 +1343,7 @@ This activity contains the following fields:
|
||||
- "software_title": Name of the software.
|
||||
- "software_package": Filename of the installer.
|
||||
- "status": Status of the software installation.
|
||||
- "source": Software source type (e.g., "pkg_packages", "sh_packages", "ps1_packages").
|
||||
- "policy_id": ID of the policy whose failure triggered the installation. Null if no associated policy.
|
||||
- "policy_name": Name of the policy whose failure triggered installation. Null if no associated policy.
|
||||
|
||||
@@ -1358,6 +1359,7 @@ This activity contains the following fields:
|
||||
"self_service": true,
|
||||
"install_uuid": "d6cffa75-b5b5-41ef-9230-15073c8a88cf",
|
||||
"status": "pending",
|
||||
"source": "pkg_packages",
|
||||
"policy_id": 1337,
|
||||
"policy_name": "Ensure 1Password is installed and up to date"
|
||||
}
|
||||
@@ -1374,6 +1376,7 @@ This activity contains the following fields:
|
||||
- "script_execution_id": ID of the software uninstall script.
|
||||
- "self_service": Whether the uninstallation was initiated by the end user from the My device UI.
|
||||
- "status": Status of the software uninstallation.
|
||||
- "source": Software source type (e.g., "pkg_packages", "sh_packages", "ps1_packages").
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -1384,7 +1387,8 @@ This activity contains the following fields:
|
||||
"software_title": "Falcon.app",
|
||||
"script_execution_id": "ece8d99d-4313-446a-9af2-e152cd1bad1e",
|
||||
"self_service": false,
|
||||
"status": "uninstalled"
|
||||
"status": "uninstalled",
|
||||
"source": "pkg_packages"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -259,12 +259,23 @@ func (svc *Service) failCancelledSetupExperienceInstalls(
|
||||
// https://github.com/fleetdm/fleet/issues/34288
|
||||
if r.IsForSoftwarePackage() {
|
||||
softwarePackage := ""
|
||||
var source *string
|
||||
installerMeta, err := svc.ds.GetSoftwareInstallerMetadataByID(ctx, *r.SoftwareInstallerID)
|
||||
if err != nil && !fleet.IsNotFound(err) {
|
||||
return ctxerr.Wrap(ctx, err, "getting software installer metadata for cancelled setup experience software install")
|
||||
}
|
||||
if installerMeta != nil {
|
||||
softwarePackage = installerMeta.Name
|
||||
// Get the software title to retrieve the source
|
||||
if installerMeta.TitleID != nil {
|
||||
title, err := svc.ds.SoftwareTitleByID(ctx, *installerMeta.TitleID, nil, fleet.TeamFilter{})
|
||||
if err != nil && !fleet.IsNotFound(err) {
|
||||
return ctxerr.Wrap(ctx, err, "getting software title for cancelled setup experience software install")
|
||||
}
|
||||
if title != nil {
|
||||
source = &title.Source
|
||||
}
|
||||
}
|
||||
}
|
||||
activity := fleet.ActivityTypeInstalledSoftware{
|
||||
HostID: hostID,
|
||||
@@ -274,6 +285,7 @@ func (svc *Service) failCancelledSetupExperienceInstalls(
|
||||
InstallUUID: *r.HostSoftwareInstallsExecutionID,
|
||||
Status: "failed",
|
||||
SelfService: false,
|
||||
Source: source,
|
||||
FromSetupExperience: true,
|
||||
}
|
||||
err = svc.NewActivity(ctx, nil, activity)
|
||||
|
||||
@@ -1707,7 +1707,12 @@ func (svc *Service) addScriptPackageMetadata(ctx context.Context, payload *fleet
|
||||
payload.BundleIdentifier = ""
|
||||
payload.PackageIDs = nil
|
||||
payload.Extension = extension
|
||||
payload.Source = "scripts"
|
||||
switch extension {
|
||||
case "sh":
|
||||
payload.Source = "sh_packages"
|
||||
case "ps1":
|
||||
payload.Source = "ps1_packages"
|
||||
}
|
||||
|
||||
platform, err := fleet.SoftwareInstallerPlatformFromExtension(extension)
|
||||
if err != nil {
|
||||
@@ -2413,11 +2418,11 @@ func (svc *Service) SelfServiceInstallSoftwareTitle(ctx context.Context, host *f
|
||||
func packageExtensionToPlatform(ext string) string {
|
||||
var requiredPlatform string
|
||||
switch ext {
|
||||
case ".msi", ".exe":
|
||||
case ".msi", ".exe", ".ps1":
|
||||
requiredPlatform = "windows"
|
||||
case ".pkg", ".dmg", ".zip":
|
||||
requiredPlatform = "darwin"
|
||||
case ".deb", ".rpm", ".gz", ".tgz":
|
||||
case ".deb", ".rpm", ".gz", ".tgz", ".sh":
|
||||
requiredPlatform = "linux"
|
||||
default:
|
||||
return ""
|
||||
|
||||
@@ -400,7 +400,7 @@ func TestAddScriptPackageMetadata(t *testing.T) {
|
||||
require.Equal(t, "", payload.Version)
|
||||
require.Equal(t, scriptContents, payload.InstallScript)
|
||||
require.Equal(t, "linux", payload.Platform)
|
||||
require.Equal(t, "scripts", payload.Source)
|
||||
require.Equal(t, "sh_packages", payload.Source)
|
||||
require.Empty(t, payload.BundleIdentifier)
|
||||
require.Empty(t, payload.PackageIDs)
|
||||
require.NotEmpty(t, payload.StorageID)
|
||||
@@ -430,7 +430,7 @@ func TestAddScriptPackageMetadata(t *testing.T) {
|
||||
require.Equal(t, "", payload.Version)
|
||||
require.Equal(t, scriptContents, payload.InstallScript)
|
||||
require.Equal(t, "windows", payload.Platform)
|
||||
require.Equal(t, "scripts", payload.Source)
|
||||
require.Equal(t, "ps1_packages", payload.Source)
|
||||
require.Empty(t, payload.BundleIdentifier)
|
||||
require.Empty(t, payload.PackageIDs)
|
||||
require.NotEmpty(t, payload.StorageID)
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ describe("SoftwareScriptDetailsModal - ModalButtons component", () => {
|
||||
expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Rerun" }));
|
||||
expect(onRerun).toHaveBeenCalledWith(99);
|
||||
expect(onRerun).toHaveBeenCalledWith(99, true);
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
+3
-3
@@ -144,7 +144,7 @@ interface IModalButtonsProps {
|
||||
deviceAuthToken?: string;
|
||||
installResultStatus?: string;
|
||||
hostSoftwareId?: number;
|
||||
onRerun?: (id: number) => void;
|
||||
onRerun?: (id: number, isScriptPackage: boolean) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ export const ModalButtons = ({
|
||||
const onClickRerun = () => {
|
||||
// on My Device Page, where this is relevant, both will be defined
|
||||
if (onRerun && hostSoftwareId) {
|
||||
onRerun(hostSoftwareId);
|
||||
onRerun(hostSoftwareId, true); // isScriptPackage defined for copy changes
|
||||
}
|
||||
onCancel();
|
||||
};
|
||||
@@ -190,7 +190,7 @@ interface ISoftwareInstallDetailsProps {
|
||||
hostSoftware?: IHostSoftware; // for software name when not Fleet installed (not present on activity feeds)
|
||||
deviceAuthToken?: string; // My Device Page only
|
||||
onCancel: () => void;
|
||||
onRerun?: (id: number) => void; // My Device Page only
|
||||
onRerun?: (id: number, isScriptPackage?: boolean) => void; // My Device Page only
|
||||
contactUrl?: string; // My Device Page only
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import activitiesAPI, {
|
||||
import {
|
||||
resolveUninstallStatus,
|
||||
SoftwareInstallUninstallStatus,
|
||||
SCRIPT_PACKAGE_SOURCES,
|
||||
} from "interfaces/software";
|
||||
import { ActivityType, IActivityDetails } from "interfaces/activity";
|
||||
|
||||
@@ -24,6 +25,7 @@ import Pagination from "components/Pagination";
|
||||
|
||||
import VppInstallDetailsModal from "components/ActivityDetails/InstallDetails/VppInstallDetailsModal";
|
||||
import { SoftwareInstallDetailsModal } from "components/ActivityDetails/InstallDetails/SoftwareInstallDetailsModal/SoftwareInstallDetailsModal";
|
||||
import SoftwareScriptDetailsModal from "components/ActivityDetails/InstallDetails/SoftwareScriptDetailsModal/SoftwareScriptDetailsModal";
|
||||
import SoftwareUninstallDetailsModal, {
|
||||
ISWUninstallDetailsParentState,
|
||||
} from "components/ActivityDetails/InstallDetails/SoftwareUninstallDetailsModal/SoftwareUninstallDetailsModal";
|
||||
@@ -58,6 +60,10 @@ const ActivityFeed = ({
|
||||
packageInstallDetails,
|
||||
setPackageInstallDetails,
|
||||
] = useState<IActivityDetails | null>(null);
|
||||
const [
|
||||
scriptPackageDetails,
|
||||
setScriptPackageDetails,
|
||||
] = useState<IActivityDetails | null>(null);
|
||||
const [
|
||||
packageUninstallDetails,
|
||||
setPackageUninstallDetails,
|
||||
@@ -135,7 +141,11 @@ const ActivityFeed = ({
|
||||
setShowScriptDetailsModal(true);
|
||||
break;
|
||||
case ActivityType.InstalledSoftware:
|
||||
setPackageInstallDetails({ ...details });
|
||||
if (SCRIPT_PACKAGE_SOURCES.includes(details?.source || "")) {
|
||||
setScriptPackageDetails({ ...details });
|
||||
} else {
|
||||
setPackageInstallDetails({ ...details });
|
||||
}
|
||||
break;
|
||||
case ActivityType.UninstalledSoftware:
|
||||
setPackageUninstallDetails({
|
||||
@@ -256,6 +266,12 @@ const ActivityFeed = ({
|
||||
onCancel={() => setPackageInstallDetails(null)}
|
||||
/>
|
||||
)}
|
||||
{scriptPackageDetails && (
|
||||
<SoftwareScriptDetailsModal
|
||||
details={scriptPackageDetails}
|
||||
onCancel={() => setScriptPackageDetails(null)}
|
||||
/>
|
||||
)}
|
||||
{packageUninstallDetails && (
|
||||
<SoftwareUninstallDetailsModal
|
||||
{...packageUninstallDetails}
|
||||
|
||||
@@ -77,7 +77,8 @@ interface IPackageFormProps {
|
||||
gitopsCompatible?: boolean;
|
||||
}
|
||||
// application/gzip is used for .tar.gz files because browsers can't handle double-extensions correctly
|
||||
const ACCEPTED_EXTENSIONS = ".pkg,.msi,.exe,.deb,.rpm,application/gzip,.tgz";
|
||||
const ACCEPTED_EXTENSIONS =
|
||||
".pkg,.msi,.exe,.deb,.rpm,application/gzip,.tgz,.sh,.ps1";
|
||||
|
||||
const PackageForm = ({
|
||||
labels,
|
||||
|
||||
+5
-1
@@ -103,7 +103,11 @@ const SoftwareOptionsSelector = ({
|
||||
const isPlatformIosOrIpados = platform === "ios" || platform === "ipados";
|
||||
const isSelfServiceDisabled = disableOptions || isPlatformIosOrIpados;
|
||||
const isAutomaticInstallDisabled =
|
||||
disableOptions || isPlatformIosOrIpados || isExePackage || isTarballPackage;
|
||||
disableOptions ||
|
||||
isPlatformIosOrIpados ||
|
||||
isExePackage ||
|
||||
isTarballPackage ||
|
||||
isScriptPackage;
|
||||
|
||||
/** Tooltip only shows when enabled or for exe/tar.gz/sh/ps1 packages */
|
||||
const showAutomaticInstallTooltip =
|
||||
|
||||
@@ -62,6 +62,7 @@ import { IPolicy, IStoredPolicyResponse } from "interfaces/policy";
|
||||
import {
|
||||
isValidSoftwareAggregateStatus,
|
||||
SoftwareAggregateStatus,
|
||||
SCRIPT_PACKAGE_SOURCES,
|
||||
} from "interfaces/software";
|
||||
import { API_ALL_TEAMS_ID, ITeam } from "interfaces/team";
|
||||
import { IEmptyTableProps } from "interfaces/empty_table";
|
||||
@@ -2001,6 +2002,9 @@ const ManageHostsPage = ({
|
||||
onClickEditLabel={onEditLabelClick}
|
||||
onClickDeleteLabel={toggleDeleteLabelModal}
|
||||
isLoading={isLoading}
|
||||
isScriptPackage={SCRIPT_PACKAGE_SOURCES.includes(
|
||||
hostsData?.software_title?.source || ""
|
||||
)}
|
||||
/>
|
||||
{renderNoEnrollSecretBanner()}
|
||||
{renderTable()}
|
||||
|
||||
+3
-1
@@ -110,6 +110,7 @@ interface IHostsFilterBlockProps {
|
||||
onClickEditLabel: (evt: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onClickDeleteLabel: () => void;
|
||||
isLoading?: boolean;
|
||||
isScriptPackage?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,6 +165,7 @@ const HostsFilterBlock = ({
|
||||
onClickEditLabel,
|
||||
onClickDeleteLabel,
|
||||
isLoading = false,
|
||||
isScriptPackage,
|
||||
}: IHostsFilterBlockProps) => {
|
||||
const { currentUser, isOnGlobalTeam } = useContext(AppContext);
|
||||
|
||||
@@ -522,7 +524,7 @@ const HostsFilterBlock = ({
|
||||
|
||||
const renderSoftwareInstallStatusBlock = () => {
|
||||
const OPTIONS = [
|
||||
{ value: "installed", label: "Installed" },
|
||||
{ value: "installed", label: isScriptPackage ? "Ran" : "Installed" },
|
||||
{ value: "failed", label: "Failed" },
|
||||
{ value: "pending", label: "Pending" },
|
||||
];
|
||||
|
||||
+11
-2
@@ -17,6 +17,7 @@ import {
|
||||
IHostAppStoreApp,
|
||||
EnhancedSoftwareInstallUninstallStatus,
|
||||
IHostSoftwareWithUiStatus,
|
||||
SCRIPT_PACKAGE_SOURCES,
|
||||
} from "interfaces/software";
|
||||
import { IconNames } from "components/icons";
|
||||
import {
|
||||
@@ -55,7 +56,10 @@ interface IHostInstallerActionButtonProps {
|
||||
|
||||
interface IHostInstallerActionCellProps {
|
||||
software: IHostSoftwareWithUiStatus;
|
||||
onClickInstallAction: (softwareId: number) => void;
|
||||
onClickInstallAction: (
|
||||
softwareId: number,
|
||||
isSoftwarePackage?: boolean
|
||||
) => void;
|
||||
onClickUninstallAction: () => void;
|
||||
onClickOpenInstructionsAction?: () => void;
|
||||
baseClass: string;
|
||||
@@ -287,7 +291,12 @@ export const HostInstallerActionCell = ({
|
||||
baseClass={baseClass}
|
||||
tooltip={installTooltip}
|
||||
disabled={installDisabled}
|
||||
onClick={() => onClickInstallAction(id)}
|
||||
onClick={() =>
|
||||
onClickInstallAction(
|
||||
id,
|
||||
SCRIPT_PACKAGE_SOURCES.includes(software.source)
|
||||
)
|
||||
}
|
||||
icon={buttonDisplayConfig.install.icon}
|
||||
text={buttonDisplayConfig.install.text}
|
||||
testId={`${baseClass}__install-button--test`}
|
||||
|
||||
@@ -434,20 +434,30 @@ const HostSoftwareLibrary = ({
|
||||
}, []);
|
||||
|
||||
const onClickInstallAction = useCallback(
|
||||
async (softwareId: number) => {
|
||||
async (softwareId: number, isScriptPackage = false) => {
|
||||
try {
|
||||
await hostAPI.installHostSoftwarePackage(id as number, softwareId);
|
||||
if (isMountedRef.current) {
|
||||
onInstallOrUninstall();
|
||||
}
|
||||
|
||||
const message = () => {
|
||||
switch (true) {
|
||||
case isHostOnline && isScriptPackage:
|
||||
return "Script is running.";
|
||||
case isHostOnline && !isScriptPackage:
|
||||
return "Software is installing.";
|
||||
case !isHostOnline && isScriptPackage:
|
||||
return "Script will run when the host comes online.";
|
||||
default:
|
||||
return "Software will install when the host comes online.";
|
||||
}
|
||||
};
|
||||
|
||||
renderFlash(
|
||||
"success",
|
||||
<>
|
||||
Software{" "}
|
||||
{isHostOnline
|
||||
? "is installing"
|
||||
: "will install when the host comes online"}
|
||||
. To see details, go to <b>Details > Activity</b>.
|
||||
{message()} To see details, go to <b>Details > Activity</b>.
|
||||
</>
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ interface IHostSWLibraryTableHeaders {
|
||||
details?: ISWUninstallDetailsParentState
|
||||
) => void;
|
||||
onSetSelectedVPPInstallDetails: (s: IVPPHostSoftware) => void;
|
||||
onClickInstallAction: (softwareId: number) => void;
|
||||
onClickInstallAction: (softwareId: number, isScriptPackage?: boolean) => void;
|
||||
onClickUninstallAction: (softwareId: number) => void;
|
||||
isHostOnline: boolean;
|
||||
hostName: string;
|
||||
|
||||
@@ -373,7 +373,7 @@ const getEmptyCellTooltip = (
|
||||
|
||||
return (
|
||||
<>
|
||||
{softwareName ? <b>{softwareName}</b> : "Software"} can be
|
||||
{softwareName ? <b>{softwareName}</b> : "Software"} can be{" "}
|
||||
{isScriptPackage ? "ran" : "installed"} on the host.
|
||||
<br /> Select <b>Actions > Install</b> to install.
|
||||
</>
|
||||
|
||||
@@ -350,7 +350,7 @@ const SoftwareSelfService = ({
|
||||
}, []);
|
||||
|
||||
const onClickInstallAction = useCallback(
|
||||
async (softwareId: number) => {
|
||||
async (softwareId: number, isScriptPackage = false) => {
|
||||
try {
|
||||
await deviceApi.installSelfServiceSoftware(deviceToken, softwareId);
|
||||
if (isMountedRef.current) {
|
||||
@@ -358,7 +358,10 @@ const SoftwareSelfService = ({
|
||||
}
|
||||
} catch (error) {
|
||||
// We only show toast message if API returns an error
|
||||
renderFlash("error", "Couldn't install. Please try again.");
|
||||
renderFlash(
|
||||
"error",
|
||||
`Couldn't ${isScriptPackage ? "run" : "install"}. Please try again.`
|
||||
);
|
||||
}
|
||||
},
|
||||
[deviceToken, onInstallOrUninstall, renderFlash]
|
||||
|
||||
@@ -46,7 +46,7 @@ interface ISelfServiceTableHeaders {
|
||||
onShowUninstallDetails: (
|
||||
uninstallDetails: ISWUninstallDetailsParentState
|
||||
) => void;
|
||||
onClickInstallAction: (softwareId: number) => void;
|
||||
onClickInstallAction: (softwareId: number, isScriptPackage?: boolean) => void;
|
||||
onClickUninstallAction: (software: IHostSoftwareWithUiStatus) => void;
|
||||
onClickOpenInstructionsAction: (software: IHostSoftwareWithUiStatus) => void;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
IHostSoftware,
|
||||
ISoftware,
|
||||
SoftwareAggregateStatus,
|
||||
SoftwareSource,
|
||||
} from "interfaces/software";
|
||||
import {
|
||||
DiskEncryptionStatus,
|
||||
@@ -35,8 +36,12 @@ export interface ISortOption {
|
||||
|
||||
export interface ILoadHostsResponse {
|
||||
hosts: IHost[];
|
||||
software: ISoftware | undefined;
|
||||
software_title: { name: string; version?: string } | null | undefined; // TODO: confirm type
|
||||
software?: ISoftware;
|
||||
software_title?: {
|
||||
name: string;
|
||||
version?: string;
|
||||
source?: SoftwareSource;
|
||||
} | null;
|
||||
munki_issue: IMunkiIssuesAggregate;
|
||||
mobile_device_management_solution: IMdmSolution;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ const getDefaultInstallScript = (fileName: string): string => {
|
||||
case "rpm":
|
||||
return installRPM;
|
||||
case "exe":
|
||||
return "";
|
||||
case "tar.gz":
|
||||
case "sh":
|
||||
case "ps1":
|
||||
return "";
|
||||
default:
|
||||
throw new Error(`unsupported file extension: ${extension}`);
|
||||
|
||||
@@ -26,8 +26,9 @@ const getDefaultUninstallScript = (fileName: string): string => {
|
||||
case "rpm":
|
||||
return uninstallRPM;
|
||||
case "exe":
|
||||
return "";
|
||||
case "tar.gz":
|
||||
case "sh":
|
||||
case "ps1":
|
||||
return "";
|
||||
default:
|
||||
throw new Error(`unsupported file extension: ${extension}`);
|
||||
|
||||
@@ -379,6 +379,7 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
|
||||
'install_uuid', ua.execution_id,
|
||||
'status', 'pending_install',
|
||||
'self_service', ua.payload->'$.self_service' IS TRUE,
|
||||
'source', COALESCE(st.source, ua.payload->>'$.source'),
|
||||
'policy_id', siua.policy_id,
|
||||
'policy_name', p.name
|
||||
) as details,
|
||||
@@ -420,6 +421,7 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
|
||||
'script_execution_id', ua.execution_id,
|
||||
'status', 'pending_uninstall',
|
||||
'self_service', COALESCE(ua.payload->'$.self_service', FALSE) IS TRUE,
|
||||
'source', COALESCE(st.source, ua.payload->>'$.source'),
|
||||
'policy_id', siua.policy_id,
|
||||
'policy_name', p.name
|
||||
) as details,
|
||||
|
||||
@@ -975,7 +975,7 @@ func (ds *Datastore) InsertSoftwareInstallRequest(ctx context.Context, hostID ui
|
||||
const (
|
||||
getInstallerStmt = `
|
||||
SELECT
|
||||
filename, "version", title_id, COALESCE(st.name, '[deleted title]') title_name
|
||||
filename, "version", title_id, COALESCE(st.name, '[deleted title]') title_name, st.source
|
||||
FROM
|
||||
software_installers si
|
||||
LEFT JOIN software_titles st
|
||||
@@ -992,6 +992,7 @@ VALUES
|
||||
'installer_filename', ?,
|
||||
'version', ?,
|
||||
'software_title_name', ?,
|
||||
'source', ?,
|
||||
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = ?)
|
||||
)
|
||||
)`
|
||||
@@ -1021,6 +1022,7 @@ VALUES
|
||||
Version string `db:"version"`
|
||||
TitleID *uint `db:"title_id"`
|
||||
TitleName *string `db:"title_name"`
|
||||
Source *string `db:"source"`
|
||||
}
|
||||
if err = sqlx.GetContext(ctx, ds.reader(ctx), &installerDetails, getInstallerStmt, softwareInstallerID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -1047,6 +1049,7 @@ VALUES
|
||||
installerDetails.Filename,
|
||||
installerDetails.Version,
|
||||
installerDetails.TitleName,
|
||||
installerDetails.Source,
|
||||
userID,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1152,7 +1155,7 @@ func (ds *Datastore) runInstallerUpdateSideEffectsInTransaction(ctx context.Cont
|
||||
|
||||
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
|
||||
getInstallerStmt = `SELECT title_id, COALESCE(st.name, '[deleted title]') title_name, st.source
|
||||
FROM software_installers si LEFT JOIN software_titles st ON si.title_id = st.id WHERE si.id = ?`
|
||||
|
||||
insertUAStmt = `
|
||||
@@ -1164,6 +1167,7 @@ VALUES
|
||||
'installer_filename', '',
|
||||
'version', 'unknown',
|
||||
'software_title_name', ?,
|
||||
'source', ?,
|
||||
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = ?),
|
||||
'self_service', ?
|
||||
)
|
||||
@@ -1191,6 +1195,7 @@ VALUES
|
||||
var installerDetails struct {
|
||||
TitleID *uint `db:"title_id"`
|
||||
TitleName *string `db:"title_name"`
|
||||
Source *string `db:"source"`
|
||||
}
|
||||
if err = sqlx.GetContext(ctx, ds.reader(ctx), &installerDetails, getInstallerStmt, softwareInstallerID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -1213,6 +1218,7 @@ VALUES
|
||||
false,
|
||||
executionID,
|
||||
installerDetails.TitleName,
|
||||
installerDetails.Source,
|
||||
userID,
|
||||
selfService,
|
||||
)
|
||||
@@ -1258,7 +1264,8 @@ SELECT
|
||||
hsi.host_deleted_at,
|
||||
hsi.policy_id,
|
||||
hsi.created_at as created_at,
|
||||
hsi.updated_at as updated_at
|
||||
hsi.updated_at as updated_at,
|
||||
st.source
|
||||
FROM
|
||||
host_software_installs hsi
|
||||
LEFT JOIN software_titles st ON hsi.software_title_id = st.id
|
||||
@@ -1286,7 +1293,8 @@ SELECT
|
||||
NULL AS host_deleted_at,
|
||||
siua.policy_id AS policy_id,
|
||||
ua.created_at as created_at,
|
||||
ua.updated_at as updated_at
|
||||
ua.updated_at as updated_at,
|
||||
st.source
|
||||
FROM
|
||||
upcoming_activities ua
|
||||
INNER JOIN software_install_upcoming_activities siua
|
||||
|
||||
@@ -1837,6 +1837,7 @@ type ActivityTypeInstalledSoftware struct {
|
||||
SelfService bool `json:"self_service"`
|
||||
InstallUUID string `json:"install_uuid"`
|
||||
Status string `json:"status"`
|
||||
Source *string `json:"source,omitempty"`
|
||||
PolicyID *uint `json:"policy_id"`
|
||||
PolicyName *string `json:"policy_name"`
|
||||
FromSetupExperience bool `json:"-"`
|
||||
@@ -1864,6 +1865,7 @@ func (a ActivityTypeInstalledSoftware) Documentation() (activity, details, detai
|
||||
- "software_title": Name of the software.
|
||||
- "software_package": Filename of the installer.
|
||||
- "status": Status of the software installation.
|
||||
- "source": Software source type (e.g., "pkg_packages", "sh_packages", "ps1_packages").
|
||||
- "policy_id": ID of the policy whose failure triggered the installation. Null if no associated policy.
|
||||
- "policy_name": Name of the policy whose failure triggered installation. Null if no associated policy.
|
||||
`, `{
|
||||
@@ -1874,18 +1876,20 @@ func (a ActivityTypeInstalledSoftware) Documentation() (activity, details, detai
|
||||
"self_service": true,
|
||||
"install_uuid": "d6cffa75-b5b5-41ef-9230-15073c8a88cf",
|
||||
"status": "pending",
|
||||
"source": "pkg_packages",
|
||||
"policy_id": 1337,
|
||||
"policy_name": "Ensure 1Password is installed and up to date"
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeUninstalledSoftware struct {
|
||||
HostID uint `json:"host_id"`
|
||||
HostDisplayName string `json:"host_display_name"`
|
||||
SoftwareTitle string `json:"software_title"`
|
||||
ExecutionID string `json:"script_execution_id"`
|
||||
SelfService bool `json:"self_service"`
|
||||
Status string `json:"status"`
|
||||
HostID uint `json:"host_id"`
|
||||
HostDisplayName string `json:"host_display_name"`
|
||||
SoftwareTitle string `json:"software_title"`
|
||||
ExecutionID string `json:"script_execution_id"`
|
||||
SelfService bool `json:"self_service"`
|
||||
Status string `json:"status"`
|
||||
Source *string `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeUninstalledSoftware) ActivityName() string {
|
||||
@@ -1904,13 +1908,15 @@ func (a ActivityTypeUninstalledSoftware) Documentation() (activity, details, det
|
||||
- "software_title": Name of the software.
|
||||
- "script_execution_id": ID of the software uninstall script.
|
||||
- "self_service": Whether the uninstallation was initiated by the end user from the My device UI.
|
||||
- "status": Status of the software uninstallation.`, `{
|
||||
- "status": Status of the software uninstallation.
|
||||
- "source": Software source type (e.g., "pkg_packages", "sh_packages", "ps1_packages").`, `{
|
||||
"host_id": 1,
|
||||
"host_display_name": "Anna's MacBook Pro",
|
||||
"software_title": "Falcon.app",
|
||||
"script_execution_id": "ece8d99d-4313-446a-9af2-e152cd1bad1e",
|
||||
"self_service": false,
|
||||
"status": "uninstalled"
|
||||
"status": "uninstalled",
|
||||
"source": "pkg_packages"
|
||||
}`
|
||||
}
|
||||
|
||||
|
||||
@@ -378,6 +378,8 @@ type HostSoftwareInstallerResult struct {
|
||||
SoftwareInstallerID *uint `json:"-" db:"software_installer_id"`
|
||||
// SoftwarePackage is the name of the software installer package.
|
||||
SoftwarePackage string `json:"software_package" db:"software_package"`
|
||||
// Source is the osquery source for this software (e.g., "sh_packages", "ps1_packages").
|
||||
Source *string `json:"source" db:"source"`
|
||||
// HostID is the ID of the host.
|
||||
HostID uint `json:"host_id" db:"host_id"`
|
||||
// Status is the status of the software installer package on the host.
|
||||
@@ -584,8 +586,10 @@ func SofwareInstallerSourceFromExtensionAndName(ext, name string) (string, error
|
||||
return "pkg_packages", nil
|
||||
case "tar.gz":
|
||||
return "tgz_packages", nil
|
||||
case "sh", "ps1":
|
||||
return "scripts", nil
|
||||
case "sh":
|
||||
return "sh_packages", nil
|
||||
case "ps1":
|
||||
return "ps1_packages", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported file type: %s", ext)
|
||||
}
|
||||
|
||||
@@ -203,10 +203,10 @@ func TestSofwareInstallerSourceFromExtensionAndName(t *testing.T) {
|
||||
{"tar.gz", "archive.tar.gz", "tgz_packages", false},
|
||||
|
||||
// New script extensions
|
||||
{".sh", "script.sh", "scripts", false},
|
||||
{"sh", "setup.sh", "scripts", false},
|
||||
{".ps1", "script.ps1", "scripts", false},
|
||||
{"ps1", "setup.ps1", "scripts", false},
|
||||
{".sh", "script.sh", "sh_packages", false},
|
||||
{"sh", "setup.sh", "sh_packages", false},
|
||||
{".ps1", "script.ps1", "ps1_packages", false},
|
||||
{"ps1", "setup.ps1", "ps1_packages", false},
|
||||
|
||||
// Unsupported extensions
|
||||
{".zip", "archive.zip", "", true},
|
||||
|
||||
@@ -14201,6 +14201,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
||||
SoftwarePackage: payload.Filename,
|
||||
InstallUUID: installUUIDs[0],
|
||||
Status: string(fleet.SoftwareInstallFailed),
|
||||
Source: ptr.String("deb_packages"),
|
||||
}
|
||||
s.lastActivityMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
|
||||
@@ -14225,6 +14226,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
||||
SoftwarePackage: payload2.Filename,
|
||||
InstallUUID: installUUIDs[1],
|
||||
Status: string(fleet.SoftwareInstallFailed),
|
||||
Source: ptr.String("deb_packages"),
|
||||
}
|
||||
s.lastActivityOfTypeMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
|
||||
@@ -14255,6 +14257,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
||||
SoftwarePackage: payload3.Filename,
|
||||
InstallUUID: installUUIDs[2],
|
||||
Status: string(fleet.SoftwareInstalled),
|
||||
Source: ptr.String("deb_packages"),
|
||||
}
|
||||
lastActID := s.lastActivityOfTypeMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
|
||||
@@ -14291,6 +14294,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
||||
SoftwarePackage: payload3.Filename,
|
||||
InstallUUID: installUUIDs[2],
|
||||
Status: string(fleet.SoftwareInstallFailed),
|
||||
Source: ptr.String("deb_packages"),
|
||||
}
|
||||
s.lastActivityOfTypeMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
|
||||
@@ -16578,6 +16582,7 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
||||
"self_service": false,
|
||||
"install_uuid": "%s",
|
||||
"status": "installed",
|
||||
"source": "apps",
|
||||
"policy_id": %d,
|
||||
"policy_name": "%s"
|
||||
}`, host1Team1.ID, host1Team1.DisplayName(), "DummyApp", "dummy_installer.pkg", host1LastInstall.ExecutionID, policy1Team1.ID, policy1Team1.Name), 0)
|
||||
@@ -16598,6 +16603,7 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
||||
"self_service": false,
|
||||
"install_uuid": "%s",
|
||||
"status": "%s",
|
||||
"source": "deb_packages",
|
||||
"policy_id": %d,
|
||||
"policy_name": "%s"
|
||||
}`, host2Team1.ID, host2Team1.DisplayName(), "ruby", "ruby.deb", host2LastInstall.ExecutionID, fleet.SoftwareInstallFailed, policy2Team1.ID, policy2Team1.Name), 0)
|
||||
@@ -16640,6 +16646,7 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
||||
"self_service": false,
|
||||
"install_uuid": "%s",
|
||||
"status": "%s",
|
||||
"source": "programs",
|
||||
"policy_id": %f,
|
||||
"policy_name": "%s"
|
||||
}`, host3Team2.ID, host3Team2.DisplayName(), "Fleet osquery", "fleet-osquery.msi", host3LastInstall.ExecutionID,
|
||||
@@ -17588,6 +17595,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareUploadRPM() {
|
||||
SoftwarePackage: payload.Filename,
|
||||
InstallUUID: installUUID,
|
||||
Status: string(fleet.SoftwareInstallFailed),
|
||||
Source: ptr.String("rpm_packages"),
|
||||
}
|
||||
s.lastActivityMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
}
|
||||
@@ -18512,6 +18520,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerOrbitDownloadFailu
|
||||
SoftwarePackage: payload.Filename,
|
||||
InstallUUID: swInstallExecID,
|
||||
Status: string(fleet.SoftwareInstalled),
|
||||
Source: ptr.String("deb_packages"),
|
||||
}
|
||||
s.lastActivityMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
}
|
||||
@@ -20311,6 +20320,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
||||
"install_uuid": %q,
|
||||
"status": "failed",
|
||||
"self_service": false,
|
||||
"source": "deb_packages",
|
||||
"policy_name": null,
|
||||
"policy_id": null
|
||||
}`, ubuntuHost.ID, ubuntuHost.DisplayName(), "vim", "vim.deb", executionIDs["vim"]), 0)
|
||||
|
||||
@@ -395,6 +395,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
"self_service": false,
|
||||
"software_title": "%s",
|
||||
"software_package": "%s",
|
||||
"source": "apps",
|
||||
"host_display_name": "%s"
|
||||
}
|
||||
`, enrolledHost.ID, installUUID, getSoftwareTitleResp.SoftwareTitle.Name, getSoftwareTitleResp.SoftwareTitle.SoftwarePackage.Name, enrolledHost.DisplayName())
|
||||
@@ -484,6 +485,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
"self_service": false,
|
||||
"install_uuid": "%s",
|
||||
"status": "installed",
|
||||
"source": "apps",
|
||||
"policy_id": null,
|
||||
"policy_name": null
|
||||
}
|
||||
|
||||
@@ -1398,6 +1398,7 @@ func (svc *Service) SaveHostSoftwareInstallResult(ctx context.Context, result *f
|
||||
SoftwarePackage: hsi.SoftwarePackage,
|
||||
InstallUUID: failedExecID,
|
||||
Status: string(result.Status()),
|
||||
Source: hsi.Source,
|
||||
SelfService: hsi.SelfService,
|
||||
PolicyID: nil,
|
||||
PolicyName: nil,
|
||||
@@ -1480,6 +1481,7 @@ func (svc *Service) SaveHostSoftwareInstallResult(ctx context.Context, result *f
|
||||
SoftwarePackage: hsi.SoftwarePackage,
|
||||
InstallUUID: result.InstallUUID,
|
||||
Status: string(status),
|
||||
Source: hsi.Source,
|
||||
SelfService: hsi.SelfService,
|
||||
PolicyID: hsi.PolicyID,
|
||||
PolicyName: policyName,
|
||||
|
||||
Reference in New Issue
Block a user