mirror of
https://github.com/fleetdm/fleet
synced 2026-05-21 07:58:31 +00:00
**Related issue:** Resolves #31916 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually ## Database migrations - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [x] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Script and software installer policy automations now automatically retry up to three times on failure. * Retry attempt counters automatically reset when policies transition from failing to passing state. * Enhanced attempt tracking for improved monitoring and troubleshooting of policy automation executions. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
201 lines
7.9 KiB
Go
201 lines
7.9 KiB
Go
package test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/storage"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// CreateHostScriptUpcomingActivity creates a host script execution request
|
|
// for the provided host. It returns the upcoming activity's execution ID.
|
|
func CreateHostScriptUpcomingActivity(t *testing.T, ds fleet.Datastore, host *fleet.Host) string {
|
|
ctx := context.Background()
|
|
hsr, err := ds.NewHostScriptExecutionRequest(ctx, &fleet.HostScriptRequestPayload{
|
|
HostID: host.ID,
|
|
ScriptContents: "echo 'a'",
|
|
})
|
|
require.NoError(t, err)
|
|
return hsr.ExecutionID
|
|
}
|
|
|
|
// SetHostScriptResult sets the result of a host script queued via
|
|
// CreateHostScriptUpcomingActivity.
|
|
func SetHostScriptResult(t *testing.T, ds fleet.Datastore, host *fleet.Host, execID string, exitCode int) {
|
|
ctx := context.Background()
|
|
_, _, err := ds.SetHostScriptExecutionResult(ctx, &fleet.HostScriptResultPayload{
|
|
HostID: host.ID, ExecutionID: execID, Output: "a", ExitCode: exitCode,
|
|
}, nil) // nil = manual script run, not policy automation
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// CreateHostSoftwareInstallUpcomingActivity creates a host software install
|
|
// execution request for the provided host. It returns the upcoming activity's
|
|
// execution ID.
|
|
func CreateHostSoftwareInstallUpcomingActivity(t *testing.T, ds fleet.Datastore, host *fleet.Host, user *fleet.User) string {
|
|
ctx := context.Background()
|
|
installer, err := fleet.NewTempFileReader(strings.NewReader("echo"), t.TempDir)
|
|
require.NoError(t, err)
|
|
installerID, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
|
InstallScript: "install foo",
|
|
InstallerFile: installer,
|
|
StorageID: uuid.NewString(),
|
|
Filename: "foo.pkg",
|
|
Title: uuid.NewString(),
|
|
Source: "apps",
|
|
Version: "0.0.1",
|
|
UserID: user.ID,
|
|
UninstallScript: "uninstall foo",
|
|
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
execID, err := ds.InsertSoftwareInstallRequest(ctx, host.ID, installerID, fleet.HostSoftwareInstallOptions{})
|
|
require.NoError(t, err)
|
|
return execID
|
|
}
|
|
|
|
// SetHostSoftwareInstallResult sets the result of a host software install
|
|
// queued via CreateHostSoftwareInstallUpcomingActivity.
|
|
func SetHostSoftwareInstallResult(t *testing.T, ds fleet.Datastore, host *fleet.Host, execID string, exitCode int) {
|
|
ctx := context.Background()
|
|
_, err := ds.SetHostSoftwareInstallResult(ctx, &fleet.HostSoftwareInstallResultPayload{
|
|
HostID: host.ID,
|
|
InstallUUID: execID,
|
|
InstallScriptExitCode: &exitCode,
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// CreateHostSoftwareUninstallUpcomingActivity creates a host software uninstall
|
|
// execution request for the provided host. It returns the upcoming activity's
|
|
// execution ID.
|
|
func CreateHostSoftwareUninstallUpcomingActivity(t *testing.T, ds fleet.Datastore, host *fleet.Host, user *fleet.User) string {
|
|
ctx := context.Background()
|
|
installer, err := fleet.NewTempFileReader(strings.NewReader("echo"), t.TempDir)
|
|
require.NoError(t, err)
|
|
installerID, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
|
InstallScript: "install foo",
|
|
InstallerFile: installer,
|
|
StorageID: uuid.NewString(),
|
|
Filename: "foo.pkg",
|
|
Title: uuid.NewString(),
|
|
Source: "apps",
|
|
Version: "0.0.1",
|
|
UserID: user.ID,
|
|
UninstallScript: "uninstall foo",
|
|
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
execID := uuid.NewString()
|
|
err = ds.InsertSoftwareUninstallRequest(ctx, execID, host.ID, installerID, false)
|
|
require.NoError(t, err)
|
|
return execID
|
|
}
|
|
|
|
// SetHostSoftwareUninstallResult sets the result of a host software uninstall
|
|
// queued via CreateHostSoftwareUninstallUpcomingActivity.
|
|
func SetHostSoftwareUninstallResult(t *testing.T, ds fleet.Datastore, host *fleet.Host, execID string, exitCode int) {
|
|
ctx := context.Background()
|
|
_, _, err := ds.SetHostScriptExecutionResult(ctx, &fleet.HostScriptResultPayload{
|
|
HostID: host.ID,
|
|
ExecutionID: execID,
|
|
ExitCode: exitCode,
|
|
}, nil) // nil = manual/uninstall script, not policy automation
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// CreateHostVPPAppInstallUpcomingActivity creates a VPP app install request
|
|
// for the provided host. It returns the upcoming activity's execution ID.
|
|
// Note that test.CreateInsertGlobalVPPToken(t, ds) should be used to enable
|
|
// VPP apps (create a VPP token).
|
|
func CreateHostVPPAppInstallUpcomingActivity(t *testing.T, ds fleet.Datastore, host *fleet.Host) (execID, adamID string) {
|
|
ctx := context.Background()
|
|
// Generate a short adamID that fits within the 16 character limit
|
|
adamID = uuid.NewString()[:16]
|
|
vppApp := &fleet.VPPApp{
|
|
Name: "vpp_1", VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: adamID, Platform: fleet.MacOSPlatform}},
|
|
BundleIdentifier: adamID,
|
|
}
|
|
_, err := ds.InsertVPPAppWithTeam(ctx, vppApp, nil)
|
|
require.NoError(t, err)
|
|
execID = uuid.NewString()
|
|
err = ds.InsertHostVPPSoftwareInstall(ctx, host.ID, vppApp.VPPAppID, execID, "event-id-1", fleet.HostSoftwareInstallOptions{})
|
|
require.NoError(t, err)
|
|
return execID, adamID
|
|
}
|
|
|
|
// SetHostVPPAppInstallResult sets the result of a VPP app install queued via
|
|
// CreateHostVPPAppInstallUpcomingActivity.
|
|
// The adamID is the one for the VPP app created by that call, and status is
|
|
// one of the Apple MDM status string (Acknowledged, Error, CommandFormatError,
|
|
// etc).
|
|
func SetHostVPPAppInstallResult(t *testing.T, ds fleet.Datastore, nanods storage.CommandAndReportResultsStore, host *fleet.Host, execID, adamID, status string) {
|
|
ctx := context.Background()
|
|
ctx = context.WithValue(ctx, fleet.ActivityWebhookContextKey, true)
|
|
nanoCtx := &mdm.Request{EnrollID: &mdm.EnrollID{ID: host.UUID}, Context: ctx}
|
|
|
|
cmdRes := &mdm.CommandResults{
|
|
CommandUUID: execID,
|
|
Status: status,
|
|
Raw: []byte(`<?xml version="1.0" encoding="UTF-8"?>`),
|
|
}
|
|
err := nanods.StoreCommandReport(nanoCtx, cmdRes)
|
|
require.NoError(t, err)
|
|
err = ds.NewActivity(ctx, nil, fleet.ActivityInstalledAppStoreApp{
|
|
HostID: host.ID,
|
|
AppStoreID: adamID,
|
|
CommandUUID: execID,
|
|
Status: "Error",
|
|
}, []byte(`{}`), time.Now())
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// CreateHostInHouseAppInstallUpcomingActivity creates an in-house app install
|
|
// request for the provided host. It returns the upcoming activity's execution
|
|
// ID.
|
|
func CreateHostInHouseAppInstallUpcomingActivity(t *testing.T, ds fleet.Datastore, host *fleet.Host, user *fleet.User) (execID string) {
|
|
ctx := context.Background()
|
|
rnd := uuid.NewString()
|
|
ihaID, ihaTitleID, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
|
Filename: rnd + ".ipa",
|
|
Source: "ios_apps",
|
|
Extension: "ipa",
|
|
BundleIdentifier: "com.example." + rnd,
|
|
UserID: user.ID,
|
|
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
execID = uuid.NewString()
|
|
err = ds.InsertHostInHouseAppInstall(ctx, host.ID, ihaID, ihaTitleID, execID, fleet.HostSoftwareInstallOptions{})
|
|
require.NoError(t, err)
|
|
return execID
|
|
}
|
|
|
|
func SetHostInHouseAppInstallResult(t *testing.T, ds fleet.Datastore, nanods storage.CommandAndReportResultsStore, host *fleet.Host, execID, status string) {
|
|
ctx := context.Background()
|
|
ctx = context.WithValue(ctx, fleet.ActivityWebhookContextKey, true)
|
|
nanoCtx := &mdm.Request{EnrollID: &mdm.EnrollID{ID: host.UUID}, Context: ctx}
|
|
|
|
cmdRes := &mdm.CommandResults{
|
|
CommandUUID: execID,
|
|
Status: status,
|
|
Raw: []byte(`<?xml version="1.0" encoding="UTF-8"?>`),
|
|
}
|
|
err := nanods.StoreCommandReport(nanoCtx, cmdRes)
|
|
require.NoError(t, err)
|
|
err = ds.NewActivity(ctx, nil, fleet.ActivityTypeInstalledSoftware{
|
|
HostID: host.ID,
|
|
CommandUUID: execID,
|
|
Status: "Error",
|
|
}, []byte(`{}`), time.Now())
|
|
require.NoError(t, err)
|
|
}
|