fix: show script name in activity for setup experience script (#23944)
> Related issue: #23787 This adds the script name to both the upcoming and past activities. Demo video: https://www.youtube.com/watch?v=kLSsUZhyMC4 # 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] 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/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) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
- Fixes a bug where the name of the setup experience script was not showing up in the activity for
|
||||
that script execution.
|
||||
@@ -220,9 +220,10 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, hostUUID string
|
||||
return false, ctxerr.Errorf(ctx, "setup experience script missing content id: %d", *script.SetupExperienceScriptID)
|
||||
}
|
||||
req := &fleet.HostScriptRequestPayload{
|
||||
HostID: host.ID,
|
||||
ScriptName: script.Name,
|
||||
ScriptContentID: *script.ScriptContentID,
|
||||
HostID: host.ID,
|
||||
ScriptName: script.Name,
|
||||
ScriptContentID: *script.ScriptContentID,
|
||||
SetupExperienceScriptID: script.SetupExperienceScriptID,
|
||||
}
|
||||
res, err := svc.ds.NewHostScriptExecutionRequest(ctx, req)
|
||||
if err != nil {
|
||||
|
||||
@@ -312,7 +312,7 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
|
||||
JSON_OBJECT(
|
||||
'host_id', hsr.host_id,
|
||||
'host_display_name', COALESCE(hdn.display_name, ''),
|
||||
'script_name', COALESCE(scr.name, ''),
|
||||
'script_name', COALESCE(ses.name, COALESCE(scr.name, '')),
|
||||
'script_execution_id', hsr.execution_id,
|
||||
'async', NOT hsr.sync_request,
|
||||
'policy_id', hsr.policy_id,
|
||||
@@ -330,6 +330,8 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
|
||||
scripts scr ON scr.id = hsr.script_id
|
||||
LEFT OUTER JOIN
|
||||
host_software_installs hsi ON hsi.execution_id = hsr.execution_id
|
||||
LEFT OUTER JOIN
|
||||
setup_experience_scripts ses ON ses.id = hsr.setup_experience_script_id
|
||||
WHERE
|
||||
hsr.host_id = :host_id AND
|
||||
hsr.exit_code IS NULL AND
|
||||
|
||||
@@ -541,6 +541,15 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
|
||||
h2SelfService, err := ds.InsertSoftwareInstallRequest(noUserCtx, h2.ID, sw1Meta.InstallerID, true, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
setupExpScript := &fleet.Script{Name: "setup_experience_script", ScriptContents: "setup_experience"}
|
||||
err = ds.SetSetupExperienceScript(ctx, setupExpScript)
|
||||
require.NoError(t, err)
|
||||
ses, err := ds.GetSetupExperienceScript(ctx, h2.TeamID)
|
||||
require.NoError(t, err)
|
||||
hsr, err = ds.NewHostScriptExecutionRequest(ctx, &fleet.HostScriptRequestPayload{HostID: h2.ID, ScriptContents: "setup_experience", SetupExperienceScriptID: &ses.ID})
|
||||
require.NoError(t, err)
|
||||
h2SetupExp := hsr.ExecutionID
|
||||
|
||||
// create pending install and uninstall requests for h3 that will be deleted
|
||||
_, err = ds.InsertSoftwareInstallRequest(ctx, h3.ID, sw3Meta.InstallerID, false, nil)
|
||||
require.NoError(t, err)
|
||||
@@ -560,7 +569,8 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
|
||||
endTime = SetOrderedCreatedAtTimestamps(t, ds, endTime, "host_software_installs", "execution_id", h2SelfService)
|
||||
endTime = SetOrderedCreatedAtTimestamps(t, ds, endTime, "host_software_installs", "execution_id", h2Bar)
|
||||
endTime = SetOrderedCreatedAtTimestamps(t, ds, endTime, "host_script_results", "execution_id", h2A, h2F)
|
||||
SetOrderedCreatedAtTimestamps(t, ds, endTime, "host_vpp_software_installs", "command_uuid", vppCommand1, vppCommand2)
|
||||
endTime = SetOrderedCreatedAtTimestamps(t, ds, endTime, "host_vpp_software_installs", "command_uuid", vppCommand1, vppCommand2)
|
||||
SetOrderedCreatedAtTimestamps(t, ds, endTime, "host_script_results", "execution_id", h2SetupExp)
|
||||
|
||||
execIDsWithUser := map[string]bool{
|
||||
h1A: true,
|
||||
@@ -576,11 +586,13 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
|
||||
h2Bar: true,
|
||||
vppCommand1: true,
|
||||
vppCommand2: false,
|
||||
h2SetupExp: false,
|
||||
}
|
||||
execIDsScriptName := map[string]string{
|
||||
h1A: scr1.Name,
|
||||
h1B: scr2.Name,
|
||||
h2A: scr1.Name,
|
||||
h1A: scr1.Name,
|
||||
h1B: scr2.Name,
|
||||
h2A: scr1.Name,
|
||||
h2SetupExp: setupExpScript.Name,
|
||||
}
|
||||
execIDsSoftwareTitle := map[string]string{
|
||||
h1Fleet: "foo",
|
||||
@@ -641,10 +653,10 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 8},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{PerPage: 4},
|
||||
opts: fleet.ListOptions{PerPage: 5},
|
||||
hostID: h2.ID,
|
||||
wantExecs: []string{h2SelfService, h2Bar, h2A, vppCommand2},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false, TotalResults: 4},
|
||||
wantExecs: []string{h2SelfService, h2Bar, h2A, vppCommand2, h2SetupExp},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false, TotalResults: 5},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{},
|
||||
|
||||
@@ -39,8 +39,8 @@ func (ds *Datastore) NewHostScriptExecutionRequest(ctx context.Context, request
|
||||
|
||||
func newHostScriptExecutionRequest(ctx context.Context, tx sqlx.ExtContext, request *fleet.HostScriptRequestPayload) (*fleet.HostScriptResult, error) {
|
||||
const (
|
||||
insStmt = `INSERT INTO host_script_results (host_id, execution_id, script_content_id, output, script_id, policy_id, user_id, sync_request) VALUES (?, ?, ?, '', ?, ?, ?, ?)`
|
||||
getStmt = `SELECT hsr.id, hsr.host_id, hsr.execution_id, hsr.created_at, hsr.script_id, hsr.policy_id, hsr.user_id, hsr.sync_request, sc.contents as script_contents FROM host_script_results hsr JOIN script_contents sc WHERE sc.id = hsr.script_content_id AND hsr.id = ?`
|
||||
insStmt = `INSERT INTO host_script_results (host_id, execution_id, script_content_id, output, script_id, policy_id, user_id, sync_request, setup_experience_script_id) VALUES (?, ?, ?, '', ?, ?, ?, ?, ?)`
|
||||
getStmt = `SELECT hsr.id, hsr.host_id, hsr.execution_id, hsr.created_at, hsr.script_id, hsr.policy_id, hsr.user_id, hsr.sync_request, sc.contents as script_contents, hsr.setup_experience_script_id FROM host_script_results hsr JOIN script_contents sc WHERE sc.id = hsr.script_content_id AND hsr.id = ?`
|
||||
)
|
||||
|
||||
execID := uuid.New().String()
|
||||
@@ -52,6 +52,7 @@ func newHostScriptExecutionRequest(ctx context.Context, tx sqlx.ExtContext, requ
|
||||
request.PolicyID,
|
||||
request.UserID,
|
||||
request.SyncRequest,
|
||||
request.SetupExperienceScriptID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "new host script execution request")
|
||||
@@ -269,7 +270,8 @@ func (ds *Datastore) getHostScriptExecutionResultDB(ctx context.Context, q sqlx.
|
||||
hsr.created_at,
|
||||
hsr.user_id,
|
||||
hsr.sync_request,
|
||||
hsr.host_deleted_at
|
||||
hsr.host_deleted_at,
|
||||
hsr.setup_experience_script_id
|
||||
FROM
|
||||
host_script_results hsr
|
||||
JOIN
|
||||
|
||||
@@ -404,6 +404,32 @@ WHERE
|
||||
return &script, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetSetupExperienceScriptByID(ctx context.Context, scriptID uint) (*fleet.Script, error) {
|
||||
query := `
|
||||
SELECT
|
||||
id,
|
||||
team_id,
|
||||
name,
|
||||
script_content_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
setup_experience_scripts
|
||||
WHERE
|
||||
id = ?
|
||||
`
|
||||
|
||||
var script fleet.Script
|
||||
if err := sqlx.GetContext(ctx, ds.reader(ctx), &script, query, scriptID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("SetupExperienceScript"), "get setup experience script by id")
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "get setup experience script by id")
|
||||
}
|
||||
|
||||
return &script, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) SetSetupExperienceScript(ctx context.Context, script *fleet.Script) error {
|
||||
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
var err error
|
||||
|
||||
@@ -29,6 +29,7 @@ func TestSetupExperience(t *testing.T) {
|
||||
{"ListSetupExperienceStatusResults", testSetupExperienceStatusResults},
|
||||
{"SetupExperienceScriptCRUD", testSetupExperienceScriptCRUD},
|
||||
{"TestHostInSetupExperience", testHostInSetupExperience},
|
||||
{"TestGetSetupExperienceScriptByID", testGetSetupExperienceScriptByID},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -675,7 +676,6 @@ func testSetSetupExperienceTitles(t *testing.T, ds *Datastore) {
|
||||
assert.False(t, *titles[0].SoftwarePackage.InstallDuringSetup)
|
||||
assert.False(t, *titles[1].SoftwarePackage.InstallDuringSetup)
|
||||
assert.False(t, *titles[2].AppStoreApp.InstallDuringSetup)
|
||||
|
||||
}
|
||||
|
||||
func testSetupExperienceStatusResults(t *testing.T, ds *Datastore) {
|
||||
@@ -909,3 +909,28 @@ func testHostInSetupExperience(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
require.False(t, inSetupExperience)
|
||||
}
|
||||
|
||||
func testGetSetupExperienceScriptByID(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
script := &fleet.Script{
|
||||
Name: "setup_experience_script",
|
||||
ScriptContents: "echo hello",
|
||||
}
|
||||
|
||||
err := ds.SetSetupExperienceScript(ctx, script)
|
||||
require.NoError(t, err)
|
||||
|
||||
scriptByTeamID, err := ds.GetSetupExperienceScript(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotScript, err := ds.GetSetupExperienceScriptByID(ctx, scriptByTeamID.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, script.Name, gotScript.Name)
|
||||
require.NotZero(t, gotScript.ScriptContentID)
|
||||
|
||||
b, err := ds.GetAnyScriptContents(ctx, gotScript.ScriptContentID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, script.ScriptContents, string(b))
|
||||
}
|
||||
|
||||
@@ -1795,14 +1795,46 @@ type Datastore interface {
|
||||
// Setup Experience
|
||||
//
|
||||
|
||||
// ListSetupExperienceResultsByHostUUID lists the setup experience results for a host by its UUID.
|
||||
ListSetupExperienceResultsByHostUUID(ctx context.Context, hostUUID string) ([]*SetupExperienceStatusResult, error)
|
||||
|
||||
// UpdateSetupExperienceStatusResult updates the given setup experience status result.
|
||||
UpdateSetupExperienceStatusResult(ctx context.Context, status *SetupExperienceStatusResult) error
|
||||
|
||||
// EnqueueSetupExperienceItems enqueues the relevant setup experience items (software and
|
||||
// script) for a given host. It first clears out any pre-existing setup experience items that
|
||||
// were previously enqueued for the host (since the setup experience only happens once during
|
||||
// the initial device setup). It then adds any software and script that have been configured for
|
||||
// this team to the host's queue and sets their status to pending. If any items were enqueued,
|
||||
// it returns true, otherwise it returns false.
|
||||
EnqueueSetupExperienceItems(ctx context.Context, hostUUID string, teamID uint) (bool, error)
|
||||
|
||||
// GetSetupExperienceScript gets the setup experience script for a team. There can only be 1
|
||||
// setup experience script per team.
|
||||
GetSetupExperienceScript(ctx context.Context, teamID *uint) (*Script, error)
|
||||
|
||||
// GetSetupExperienceScriptByID gets the setup experience script by its ID.
|
||||
GetSetupExperienceScriptByID(ctx context.Context, scriptID uint) (*Script, error)
|
||||
|
||||
// SetSetupExperienceScript sets the setup experience script to the given script.
|
||||
SetSetupExperienceScript(ctx context.Context, script *Script) error
|
||||
|
||||
// DeleteSetupExperienceScript deletes the setup experience script for the given team.
|
||||
DeleteSetupExperienceScript(ctx context.Context, teamID *uint) error
|
||||
|
||||
// MaybeUpdateSetupExperienceScriptStatus updates the status of the setup experience script for
|
||||
// the given host if the script result row exists. If there was an update, it returns true.
|
||||
// Otherwise, it returns false.
|
||||
MaybeUpdateSetupExperienceScriptStatus(ctx context.Context, hostUUID string, executionID string, status SetupExperienceStatusResultStatus) (bool, error)
|
||||
|
||||
// MaybeUpdateSetupExperienceSoftwareInstallStatus updates the status of the setup experience
|
||||
// software installer for the given host if the software installer result row exists. If there
|
||||
// was an update, it returns true. Otherwise, it returns false.
|
||||
MaybeUpdateSetupExperienceSoftwareInstallStatus(ctx context.Context, hostUUID string, executionID string, status SetupExperienceStatusResultStatus) (bool, error)
|
||||
|
||||
// MaybeUpdateSetupExperienceVPPStatus updates the status of the setup experience
|
||||
// VPP app for the given host if the VPP app installer row exists. If there was an update, it
|
||||
// returns true. Otherwise, it returns false.
|
||||
MaybeUpdateSetupExperienceVPPStatus(ctx context.Context, hostUUID string, commandUUID string, status SetupExperienceStatusResultStatus) (bool, error)
|
||||
|
||||
// Fleet-maintained apps
|
||||
|
||||
@@ -149,6 +149,9 @@ type HostScriptRequestPayload struct {
|
||||
// SyncRequest is filled automatically based on the endpoint used to create
|
||||
// the execution request (synchronous or asynchronous).
|
||||
SyncRequest bool `json:"-"`
|
||||
// SetupExperienceScriptID is the ID of the setup experience script related to this request
|
||||
// payload, if such a script exists.
|
||||
SetupExperienceScriptID *uint `json:"-"`
|
||||
}
|
||||
|
||||
func (r HostScriptRequestPayload) ValidateParams(waitForResult time.Duration) error {
|
||||
@@ -251,6 +254,10 @@ type HostScriptResult struct {
|
||||
// results can still be returned to see activity details after the host got
|
||||
// deleted.
|
||||
HostDeletedAt *time.Time `json:"-" db:"host_deleted_at"`
|
||||
|
||||
// SetupExperienceScriptID is the ID of the setup experience script, if this script execution
|
||||
// was part of setup experience.
|
||||
SetupExperienceScriptID *uint `json:"-" db:"setup_experience_script_id"`
|
||||
}
|
||||
|
||||
func (hsr HostScriptResult) AuthzType() string {
|
||||
|
||||
@@ -1141,6 +1141,8 @@ type EnqueueSetupExperienceItemsFunc func(ctx context.Context, hostUUID string,
|
||||
|
||||
type GetSetupExperienceScriptFunc func(ctx context.Context, teamID *uint) (*fleet.Script, error)
|
||||
|
||||
type GetSetupExperienceScriptByIDFunc func(ctx context.Context, scriptID uint) (*fleet.Script, error)
|
||||
|
||||
type SetSetupExperienceScriptFunc func(ctx context.Context, script *fleet.Script) error
|
||||
|
||||
type DeleteSetupExperienceScriptFunc func(ctx context.Context, teamID *uint) error
|
||||
@@ -2844,6 +2846,9 @@ type DataStore struct {
|
||||
GetSetupExperienceScriptFunc GetSetupExperienceScriptFunc
|
||||
GetSetupExperienceScriptFuncInvoked bool
|
||||
|
||||
GetSetupExperienceScriptByIDFunc GetSetupExperienceScriptByIDFunc
|
||||
GetSetupExperienceScriptByIDFuncInvoked bool
|
||||
|
||||
SetSetupExperienceScriptFunc SetSetupExperienceScriptFunc
|
||||
SetSetupExperienceScriptFuncInvoked bool
|
||||
|
||||
@@ -6800,6 +6805,13 @@ func (s *DataStore) GetSetupExperienceScript(ctx context.Context, teamID *uint)
|
||||
return s.GetSetupExperienceScriptFunc(ctx, teamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetSetupExperienceScriptByID(ctx context.Context, scriptID uint) (*fleet.Script, error) {
|
||||
s.mu.Lock()
|
||||
s.GetSetupExperienceScriptByIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetSetupExperienceScriptByIDFunc(ctx, scriptID)
|
||||
}
|
||||
|
||||
func (s *DataStore) SetSetupExperienceScript(ctx context.Context, script *fleet.Script) error {
|
||||
s.mu.Lock()
|
||||
s.SetSetupExperienceScriptFuncInvoked = true
|
||||
|
||||
@@ -1952,6 +1952,7 @@ func (s *integrationMDMTestSuite) createTeamDeviceForSetupExperienceWithProfileS
|
||||
require.Len(t, listHostsRes.Hosts, 1)
|
||||
require.Equal(t, listHostsRes.Hosts[0].HardwareSerial, teamDevice.SerialNumber)
|
||||
enrolledHost := listHostsRes.Hosts[0].Host
|
||||
enrolledHost.TeamID = &tm.ID
|
||||
|
||||
// transfer it to the team
|
||||
s.Do("POST", "/api/v1/fleet/hosts/transfer",
|
||||
@@ -2076,6 +2077,57 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
|
||||
// The /setup_experience/status endpoint doesn't return the various IDs for executions, so pull
|
||||
// it out manually
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID)
|
||||
require.Len(t, results, 2)
|
||||
require.NoError(t, err)
|
||||
var installUUID string
|
||||
for _, r := range results {
|
||||
if r.HostSoftwareInstallsExecutionID != nil {
|
||||
installUUID = *r.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
|
||||
require.NotEmpty(t, installUUID)
|
||||
|
||||
// Need to get the software title to get the package name
|
||||
var getSoftwareTitleResp getSoftwareTitleResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", *statusResp.Results.Software[0].SoftwareTitleID), nil, http.StatusOK, &getSoftwareTitleResp, "team_id", fmt.Sprintf("%d", *enrolledHost.TeamID))
|
||||
require.NotNil(t, getSoftwareTitleResp.SoftwareTitle)
|
||||
require.NotNil(t, getSoftwareTitleResp.SoftwareTitle.SoftwarePackage)
|
||||
|
||||
debugPrintActivities := func(activities []*fleet.Activity) []string {
|
||||
var res []string
|
||||
for _, activity := range activities {
|
||||
res = append(res, fmt.Sprintf("%+v", activity))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Check upcoming activities: we should only have the software upcoming because we don't run the
|
||||
// script until after the software is done
|
||||
var hostActivitiesResp listHostUpcomingActivitiesResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/activities/upcoming", enrolledHost.ID),
|
||||
nil, http.StatusOK, &hostActivitiesResp)
|
||||
|
||||
expectedActivityDetail := fmt.Sprintf(`
|
||||
{
|
||||
"status": "pending_install",
|
||||
"host_id": %d,
|
||||
"policy_id": null,
|
||||
"policy_name": null,
|
||||
"install_uuid": "%s",
|
||||
"self_service": false,
|
||||
"software_title": "%s",
|
||||
"software_package": "%s",
|
||||
"host_display_name": "%s"
|
||||
}
|
||||
`, enrolledHost.ID, installUUID, getSoftwareTitleResp.SoftwareTitle.Name, getSoftwareTitleResp.SoftwareTitle.SoftwarePackage.Name, enrolledHost.DisplayName())
|
||||
require.Len(t, hostActivitiesResp.Activities, 1, "got activities: %v", debugPrintActivities(hostActivitiesResp.Activities))
|
||||
require.NotNil(t, hostActivitiesResp.Activities[0].Details)
|
||||
require.JSONEq(t, expectedActivityDetail, string(*hostActivitiesResp.Activities[0].Details))
|
||||
|
||||
// no MDM command got enqueued due to the /status call (device not released yet)
|
||||
cmd, err = mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
@@ -2093,20 +2145,6 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
|
||||
// The /setup_experience/status endpoint doesn't return the various IDs for executions, so pull
|
||||
// it out manually
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID)
|
||||
require.Len(t, results, 2)
|
||||
require.NoError(t, err)
|
||||
var installUUID string
|
||||
for _, r := range results {
|
||||
if r.HostSoftwareInstallsExecutionID != nil {
|
||||
installUUID = *r.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
|
||||
require.NotEmpty(t, installUUID)
|
||||
|
||||
// record a result for software installation
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result",
|
||||
json.RawMessage(fmt.Sprintf(`{
|
||||
@@ -2137,12 +2175,10 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
// Software is installed, now we should run the script
|
||||
statusResp = getOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
// Software is now running, script is still pending
|
||||
require.Equal(t, "DummyApp.app", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, statusResp.Results.Script.Status)
|
||||
@@ -2158,6 +2194,48 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
}
|
||||
}
|
||||
|
||||
// Validate past activity for software install
|
||||
// For some reason the display name that's included in the `enrolledHost` is _slightly_
|
||||
// different than the expected value in the activities. Pulling the host directly gets the
|
||||
// correct display name.
|
||||
var getHostResp getHostResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", enrolledHost.ID), nil, http.StatusOK, &getHostResp)
|
||||
|
||||
expectedActivityDetail = fmt.Sprintf(`
|
||||
{
|
||||
"host_id": %d,
|
||||
"host_display_name": "%s",
|
||||
"software_title": "%s",
|
||||
"software_package": "%s",
|
||||
"self_service": false,
|
||||
"install_uuid": "%s",
|
||||
"status": "installed",
|
||||
"policy_id": null,
|
||||
"policy_name": null
|
||||
}
|
||||
`, enrolledHost.ID, getHostResp.Host.DisplayName, statusResp.Results.Software[0].Name, getSoftwareTitleResp.SoftwareTitle.SoftwarePackage.Name, installUUID)
|
||||
|
||||
s.lastActivityMatches(fleet.ActivityTypeInstalledSoftware{}.ActivityName(), expectedActivityDetail, 0)
|
||||
|
||||
// Validate upcoming activity for the script
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/activities/upcoming", enrolledHost.ID),
|
||||
nil, http.StatusOK, &hostActivitiesResp)
|
||||
|
||||
expectedActivityDetail = fmt.Sprintf(`
|
||||
{
|
||||
"async": true,
|
||||
"host_id": %d,
|
||||
"policy_id": null,
|
||||
"policy_name": null,
|
||||
"script_name": "%s",
|
||||
"host_display_name": "%s",
|
||||
"script_execution_id": "%s"
|
||||
}
|
||||
`, enrolledHost.ID, statusResp.Results.Script.Name, enrolledHost.DisplayName(), execID)
|
||||
require.Len(t, hostActivitiesResp.Activities, 1, "got activities: %v", debugPrintActivities(hostActivitiesResp.Activities))
|
||||
require.NotNil(t, hostActivitiesResp.Activities[0].Details)
|
||||
require.JSONEq(t, expectedActivityDetail, string(*hostActivitiesResp.Activities[0].Details))
|
||||
|
||||
// record a result for script execution
|
||||
var scriptResp orbitPostScriptResultResponse
|
||||
s.DoJSON("POST", "/api/fleet/orbit/scripts/result",
|
||||
@@ -2168,7 +2246,6 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
// release of the device, as all setup experience steps are now complete.
|
||||
statusResp = getOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
// Software is now running, script is still pending
|
||||
require.Equal(t, "DummyApp.app", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
@@ -2202,6 +2279,21 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
||||
}
|
||||
require.Equal(t, 1, deviceConfiguredCount)
|
||||
require.Equal(t, 0, otherCount)
|
||||
|
||||
// Validate activity for script run
|
||||
expectedActivityDetail = fmt.Sprintf(`
|
||||
{
|
||||
"async": true,
|
||||
"host_id": %d,
|
||||
"policy_id": null,
|
||||
"policy_name": null,
|
||||
"script_name": "%s",
|
||||
"host_display_name": "%s",
|
||||
"script_execution_id": "%s"
|
||||
}
|
||||
`, enrolledHost.ID, statusResp.Results.Script.Name, getHostResp.Host.DisplayName, execID)
|
||||
|
||||
s.lastActivityMatches(fleet.ActivityTypeRanScript{}.ActivityName(), expectedActivityDetail, 0)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptForceRelease() {
|
||||
@@ -2360,7 +2452,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptFo
|
||||
require.Equal(t, 0, otherCount)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestReenrollingADEDeviceAfterRemovingtFromABM() {
|
||||
func (s *integrationMDMTestSuite) TestReenrollingADEDeviceAfterRemovingItFromABM() {
|
||||
t := s.T()
|
||||
s.enableABM(t.Name())
|
||||
ctx := context.Background()
|
||||
@@ -2534,7 +2626,6 @@ func (s *integrationMDMTestSuite) TestReenrollingADEDeviceAfterRemovingtFromABM(
|
||||
{SerialNumber: mdmDevice.SerialNumber, Model: "MacBook Pro", OS: "osx", OpType: "deleted", OpDate: time.Now()},
|
||||
}
|
||||
|
||||
t.Log("RUN AFTER DELETED")
|
||||
s.runDEPSchedule()
|
||||
|
||||
a := checkHostDEPAssignProfileResponses([]string{mdmDevice.SerialNumber}, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
@@ -2550,7 +2641,6 @@ func (s *integrationMDMTestSuite) TestReenrollingADEDeviceAfterRemovingtFromABM(
|
||||
{SerialNumber: mdmDevice.SerialNumber, Model: "MacBook Pro", OS: "osx", OpType: "added", OpDate: time.Now(), ProfileUUID: a[mdmDevice.SerialNumber].ProfileUUID},
|
||||
}
|
||||
|
||||
t.Log("RUN AFTER RE-ADDED")
|
||||
s.runDEPSchedule()
|
||||
|
||||
a = checkHostDEPAssignProfileResponses([]string{mdmDevice.SerialNumber}, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
|
||||
+10
-1
@@ -837,11 +837,20 @@ func (svc *Service) SaveHostScriptResult(ctx context.Context, result *fleet.Host
|
||||
}
|
||||
}
|
||||
var scriptName string
|
||||
if hsr.ScriptID != nil {
|
||||
|
||||
switch {
|
||||
case hsr.ScriptID != nil:
|
||||
scr, err := svc.ds.Script(ctx, *hsr.ScriptID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "get saved script")
|
||||
}
|
||||
scriptName = scr.Name
|
||||
case hsr.SetupExperienceScriptID != nil:
|
||||
scr, err := svc.ds.GetSetupExperienceScriptByID(ctx, *hsr.SetupExperienceScriptID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "get setup experience script")
|
||||
}
|
||||
|
||||
scriptName = scr.Name
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user