mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
incorporate display name into setup experience ordering and enforce 1 at a time execution (#42393)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #41741 # 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), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## 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 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Software setup items are now ordered using custom display names when available. * **Bug Fixes** * Software installations now process sequentially for improved reliability and predictability. * Enhanced handling of missing installation tracking data to prevent failures. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ian Littman <iansltx@gmail.com>
This commit is contained in:
parent
344e4f2dcd
commit
1b95a581f6
17 changed files with 697 additions and 267 deletions
1
changes/41741-order
Normal file
1
changes/41741-order
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Updated ordering of setup experience software to take display names into account.
|
||||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
)
|
||||
|
||||
func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
|
||||
|
|
@ -313,7 +314,7 @@ func (svc *Service) getHostSetupExperienceStatus(ctx context.Context, host *flee
|
|||
}
|
||||
|
||||
// Get current status of the setup experience.
|
||||
results, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID)
|
||||
results, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID, ptr.ValOrZero(host.TeamID))
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "listing setup experience results")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ func (svc *Service) GetOrbitSetupExperienceStatus(ctx context.Context, orbitNode
|
|||
}
|
||||
|
||||
// get status of software installs and script execution
|
||||
res, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID)
|
||||
res, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID, ptr.ValOrZero(host.TeamID))
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "listing setup experience results")
|
||||
}
|
||||
|
|
@ -153,19 +153,15 @@ func (svc *Service) GetOrbitSetupExperienceStatus(ctx context.Context, orbitNode
|
|||
// then re-enqueue any cancelled setup experience steps.
|
||||
if hasFailedSoftwareInstall {
|
||||
if resetFailedSetupSteps {
|
||||
teamID := uint(0)
|
||||
if host.TeamID != nil {
|
||||
teamID = *host.TeamID
|
||||
}
|
||||
// If so, call the enqueue function with a flag to retain successful steps.
|
||||
if requireAllSoftware {
|
||||
svc.logger.InfoContext(ctx, "re-enqueueing cancelled setup experience steps after a previous software install failure", "host_uuid", host.UUID)
|
||||
_, err := svc.ds.ResetSetupExperienceItemsAfterFailure(ctx, host.Platform, host.PlatformLike, host.UUID, teamID)
|
||||
_, err := svc.ds.ResetSetupExperienceItemsAfterFailure(ctx, host.Platform, host.PlatformLike, host.UUID, ptr.ValOrZero(host.TeamID))
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "re-enqueueing cancelled setup experience steps after a previous software install failure")
|
||||
}
|
||||
// Re-fetch the setup experience results after re-enqueuing.
|
||||
res, err = svc.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID)
|
||||
res, err = svc.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID, ptr.ValOrZero(host.TeamID))
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "listing setup experience results")
|
||||
}
|
||||
|
|
@ -277,7 +273,7 @@ func (svc *Service) failCancelledSetupExperienceInstalls(
|
|||
HostDisplayName: hostDisplayName,
|
||||
SoftwareTitle: r.Name,
|
||||
SoftwarePackage: softwarePackage,
|
||||
InstallUUID: *r.HostSoftwareInstallsExecutionID,
|
||||
InstallUUID: ptr.ValOrZero(r.HostSoftwareInstallsExecutionID),
|
||||
Status: "failed",
|
||||
SelfService: false,
|
||||
Source: source,
|
||||
|
|
|
|||
|
|
@ -186,13 +186,19 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, host *fleet.Hos
|
|||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "failed to get host's UUID for the setup experience")
|
||||
}
|
||||
statuses, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID)
|
||||
statuses, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID, ptr.ValOrZero(host.TeamID))
|
||||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "retrieving setup experience status results for next step")
|
||||
}
|
||||
|
||||
var installersPending, appsPending, scriptsPending []*fleet.SetupExperienceStatusResult
|
||||
var installersRunning, appsRunning, scriptsRunning int
|
||||
// Software (installers and VPP apps) are treated as a single group,
|
||||
// ordered alphabetically by display name (falling back to name). This
|
||||
// ordering is determined at enqueue time by enqueueSetupExperienceItems,
|
||||
// which inserts them with auto-incremented IDs in the correct order.
|
||||
// ListSetupExperienceResultsByHostUUID returns rows ordered by sesr.id.
|
||||
// Scripts always run after all software is done.
|
||||
var softwarePending, scriptsPending []*fleet.SetupExperienceStatusResult
|
||||
var softwareRunning, scriptsRunning int
|
||||
|
||||
for _, status := range statuses {
|
||||
if err := status.IsValid(); err != nil {
|
||||
|
|
@ -200,21 +206,14 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, host *fleet.Hos
|
|||
}
|
||||
|
||||
switch {
|
||||
case status.SoftwareInstallerID != nil:
|
||||
case status.IsForSoftware():
|
||||
switch status.Status {
|
||||
case fleet.SetupExperienceStatusPending:
|
||||
installersPending = append(installersPending, status)
|
||||
softwarePending = append(softwarePending, status)
|
||||
case fleet.SetupExperienceStatusRunning:
|
||||
installersRunning++
|
||||
softwareRunning++
|
||||
}
|
||||
case status.VPPAppTeamID != nil:
|
||||
switch status.Status {
|
||||
case fleet.SetupExperienceStatusPending:
|
||||
appsPending = append(appsPending, status)
|
||||
case fleet.SetupExperienceStatusRunning:
|
||||
appsRunning++
|
||||
}
|
||||
case status.SetupExperienceScriptID != nil:
|
||||
case status.IsForScript():
|
||||
switch status.Status {
|
||||
case fleet.SetupExperienceStatusPending:
|
||||
scriptsPending = append(scriptsPending, status)
|
||||
|
|
@ -225,38 +224,41 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, host *fleet.Hos
|
|||
}
|
||||
|
||||
switch {
|
||||
case len(installersPending) > 0:
|
||||
// enqueue installers
|
||||
for _, installer := range installersPending {
|
||||
installUUID, err := svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, *installer.SoftwareInstallerID, fleet.HostSoftwareInstallOptions{
|
||||
case len(softwarePending) > 0 && softwareRunning == 0:
|
||||
// Enqueue only the first pending software item (installer or VPP app).
|
||||
// On the next call, this item will be in "running" state and the next
|
||||
// pending item will be picked up. This ensures software is installed
|
||||
// one at a time in the alphabetical display-name order determined at
|
||||
// enqueue time (rows are ordered by sesr.id).
|
||||
sw := softwarePending[0]
|
||||
|
||||
switch {
|
||||
case sw.SoftwareInstallerID != nil:
|
||||
installUUID, err := svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, *sw.SoftwareInstallerID, fleet.HostSoftwareInstallOptions{
|
||||
SelfService: false,
|
||||
ForSetupExperience: true,
|
||||
})
|
||||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "queueing setup experience install request")
|
||||
}
|
||||
installer.HostSoftwareInstallsExecutionID = &installUUID
|
||||
installer.Status = fleet.SetupExperienceStatusRunning
|
||||
if err := svc.ds.UpdateSetupExperienceStatusResult(ctx, installer); err != nil {
|
||||
sw.HostSoftwareInstallsExecutionID = &installUUID
|
||||
sw.Status = fleet.SetupExperienceStatusRunning
|
||||
if err := svc.ds.UpdateSetupExperienceStatusResult(ctx, sw); err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "updating setup experience result with install uuid")
|
||||
}
|
||||
}
|
||||
case installersRunning == 0 && len(appsPending) > 0:
|
||||
// enqueue vpp apps
|
||||
var skipRemainingVPPInstalls bool
|
||||
enqueueVPPApps:
|
||||
for _, app := range appsPending {
|
||||
vppAppID, err := app.VPPAppID()
|
||||
|
||||
case sw.VPPAppTeamID != nil:
|
||||
vppAppID, err := sw.VPPAppID()
|
||||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "constructing vpp app details for installation")
|
||||
}
|
||||
|
||||
if app.SoftwareTitleID == nil {
|
||||
return false, ctxerr.Errorf(ctx, "setup experience software title id missing from vpp app install request: %d", app.ID)
|
||||
if sw.SoftwareTitleID == nil {
|
||||
return false, ctxerr.Errorf(ctx, "setup experience software title id missing from vpp app install request: %d", sw.ID)
|
||||
}
|
||||
|
||||
vppApp := &fleet.VPPApp{
|
||||
TitleID: *app.SoftwareTitleID,
|
||||
TitleID: *sw.SoftwareTitleID,
|
||||
VPPAppTeam: fleet.VPPAppTeam{
|
||||
VPPAppID: *vppAppID,
|
||||
},
|
||||
|
|
@ -267,16 +269,13 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, host *fleet.Hos
|
|||
ForSetupExperience: true,
|
||||
})
|
||||
|
||||
app.NanoCommandUUID = &cmdUUID
|
||||
app.Status = fleet.SetupExperienceStatusRunning
|
||||
|
||||
if err != nil {
|
||||
// if we get an error (e.g. no available licenses) while attempting to enqueue the
|
||||
// install, then we should immediately go to an error state so setup experience
|
||||
// isn't blocked.
|
||||
svc.logger.WarnContext(ctx, "got an error when attempting to enqueue VPP app install", "err", err, "adam_id", app.VPPAppAdamID)
|
||||
app.Status = fleet.SetupExperienceStatusFailure
|
||||
app.Error = ptr.String(err.Error())
|
||||
svc.logger.WarnContext(ctx, "got an error when attempting to enqueue VPP app install", "err", err, "adam_id", sw.VPPAppAdamID)
|
||||
sw.Status = fleet.SetupExperienceStatusFailure
|
||||
sw.Error = ptr.String(err.Error())
|
||||
// At this point we need to check whether the "cancel if software install fails" setting is active,
|
||||
// in which case we'll cancel the remaining pending items.
|
||||
requireAllSoftware, err := svc.IsAllSetupExperienceSoftwareRequired(ctx, host)
|
||||
|
|
@ -288,17 +287,16 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, host *fleet.Hos
|
|||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "cancelling remaining setup experience steps after vpp app install failure")
|
||||
}
|
||||
skipRemainingVPPInstalls = true
|
||||
}
|
||||
} else {
|
||||
sw.NanoCommandUUID = &cmdUUID
|
||||
sw.Status = fleet.SetupExperienceStatusRunning
|
||||
}
|
||||
if err := svc.ds.UpdateSetupExperienceStatusResult(ctx, app); err != nil {
|
||||
if err := svc.ds.UpdateSetupExperienceStatusResult(ctx, sw); err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "updating setup experience with vpp install command uuid")
|
||||
}
|
||||
if skipRemainingVPPInstalls {
|
||||
break enqueueVPPApps
|
||||
}
|
||||
}
|
||||
case installersRunning == 0 && appsRunning == 0 && len(scriptsPending) > 0:
|
||||
case softwareRunning == 0 && len(scriptsPending) > 0:
|
||||
// enqueue scripts
|
||||
for _, script := range scriptsPending {
|
||||
if script.ScriptContentID == nil {
|
||||
|
|
@ -323,7 +321,7 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, host *fleet.Hos
|
|||
return false, ctxerr.Wrap(ctx, err, "updating setup experience script execution id")
|
||||
}
|
||||
}
|
||||
case installersRunning == 0 && appsRunning == 0 && scriptsRunning == 0:
|
||||
case softwareRunning == 0 && scriptsRunning == 0:
|
||||
// finished
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func TestSetupExperienceNextStep(t *testing.T) {
|
|||
}
|
||||
|
||||
var mockListSetupExperience []*fleet.SetupExperienceStatusResult
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string, teamID uint) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
return mockListSetupExperience, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,21 +97,29 @@ WHERE host_uuid = ? AND %s`
|
|||
stmtClearSetupStatus = fmt.Sprintf(stmtClearSetupStatus, "TRUE")
|
||||
}
|
||||
|
||||
// stmtSoftwareInstallers query currently supports installers for macOS and Linux.
|
||||
stmtSoftwareInstallers := `
|
||||
INSERT INTO setup_experience_status_results (
|
||||
host_uuid,
|
||||
name,
|
||||
status,
|
||||
software_installer_id
|
||||
) SELECT
|
||||
?,
|
||||
st.name,
|
||||
'pending',
|
||||
si.id
|
||||
// Build combined software query (installers + VPP apps) before the transaction.
|
||||
fleetPlatform := fleet.PlatformFromHost(hostPlatformLike)
|
||||
|
||||
var softwareUnionParts []string
|
||||
var softwareArgs []any
|
||||
|
||||
includeSoftwareInstallers := fleetPlatform != "ios" && fleetPlatform != "ipados"
|
||||
includeVPPApps := fleetPlatform == "darwin" || fleetPlatform == "ios" || fleetPlatform == "ipados"
|
||||
|
||||
if includeSoftwareInstallers {
|
||||
installerSelect := `
|
||||
SELECT
|
||||
? AS host_uuid,
|
||||
st.name AS name,
|
||||
'pending' AS status,
|
||||
si.id AS software_installer_id,
|
||||
NULL AS vpp_app_team_id,
|
||||
COALESCE(stdn.display_name, st.name) AS sort_name
|
||||
FROM software_installers si
|
||||
INNER JOIN software_titles st
|
||||
ON si.title_id = st.id
|
||||
LEFT JOIN software_title_display_names stdn
|
||||
ON stdn.software_title_id = st.id AND stdn.team_id = ?
|
||||
WHERE install_during_setup = true
|
||||
AND global_or_team_id = ?
|
||||
AND si.is_active = TRUE
|
||||
|
|
@ -138,41 +146,66 @@ AND (
|
|||
)
|
||||
)
|
||||
)
|
||||
AND %s ORDER BY st.name ASC
|
||||
`
|
||||
if resetFailedSetupSteps {
|
||||
stmtSoftwareInstallers = fmt.Sprintf(stmtSoftwareInstallers, "si.id NOT IN (SELECT software_installer_id FROM setup_experience_status_results WHERE host_uuid = ? AND status = 'success' AND software_installer_id IS NOT NULL)")
|
||||
} else {
|
||||
stmtSoftwareInstallers = fmt.Sprintf(stmtSoftwareInstallers, "TRUE")
|
||||
AND %s`
|
||||
if resetFailedSetupSteps {
|
||||
installerSelect = fmt.Sprintf(installerSelect, "si.id NOT IN (SELECT software_installer_id FROM setup_experience_status_results WHERE host_uuid = ? AND status = 'success' AND software_installer_id IS NOT NULL)")
|
||||
} else {
|
||||
installerSelect = fmt.Sprintf(installerSelect, "TRUE")
|
||||
}
|
||||
softwareUnionParts = append(softwareUnionParts, installerSelect)
|
||||
softwareArgs = append(softwareArgs, hostUUID, teamID, teamID, fleetPlatform, hostPlatformLike, hostPlatformLike)
|
||||
if resetFailedSetupSteps {
|
||||
softwareArgs = append(softwareArgs, hostUUID)
|
||||
}
|
||||
}
|
||||
|
||||
stmtVPPApps := `
|
||||
INSERT INTO setup_experience_status_results (
|
||||
host_uuid,
|
||||
name,
|
||||
status,
|
||||
vpp_app_team_id
|
||||
) SELECT
|
||||
?,
|
||||
st.name,
|
||||
'pending',
|
||||
vat.id
|
||||
if includeVPPApps {
|
||||
vppSelect := `
|
||||
SELECT
|
||||
? AS host_uuid,
|
||||
st.name AS name,
|
||||
'pending' AS status,
|
||||
NULL AS software_installer_id,
|
||||
vat.id AS vpp_app_team_id,
|
||||
COALESCE(stdn.display_name, st.name) AS sort_name
|
||||
FROM vpp_apps va
|
||||
INNER JOIN vpp_apps_teams vat
|
||||
ON vat.adam_id = va.adam_id
|
||||
AND vat.platform = va.platform
|
||||
INNER JOIN software_titles st
|
||||
ON va.title_id = st.id
|
||||
LEFT JOIN software_title_display_names stdn
|
||||
ON stdn.software_title_id = st.id AND stdn.team_id = ?
|
||||
WHERE vat.install_during_setup = true
|
||||
AND vat.global_or_team_id = ?
|
||||
AND va.platform = ?
|
||||
AND %s
|
||||
ORDER BY st.name ASC
|
||||
`
|
||||
if resetFailedSetupSteps {
|
||||
stmtVPPApps = fmt.Sprintf(stmtVPPApps, "vat.id NOT IN (SELECT vpp_app_team_id FROM setup_experience_status_results WHERE host_uuid = ? AND status = 'success' AND vpp_app_team_id IS NOT NULL)")
|
||||
} else {
|
||||
stmtVPPApps = fmt.Sprintf(stmtVPPApps, "TRUE")
|
||||
AND %s`
|
||||
if resetFailedSetupSteps {
|
||||
vppSelect = fmt.Sprintf(vppSelect, "vat.id NOT IN (SELECT vpp_app_team_id FROM setup_experience_status_results WHERE host_uuid = ? AND status = 'success' AND vpp_app_team_id IS NOT NULL)")
|
||||
} else {
|
||||
vppSelect = fmt.Sprintf(vppSelect, "TRUE")
|
||||
}
|
||||
softwareUnionParts = append(softwareUnionParts, vppSelect)
|
||||
softwareArgs = append(softwareArgs, hostUUID, teamID, teamID, fleetPlatform)
|
||||
if resetFailedSetupSteps {
|
||||
softwareArgs = append(softwareArgs, hostUUID)
|
||||
}
|
||||
}
|
||||
|
||||
var stmtSoftwareCombined string
|
||||
if len(softwareUnionParts) > 0 {
|
||||
stmtSoftwareCombined = fmt.Sprintf(`
|
||||
INSERT INTO setup_experience_status_results (
|
||||
host_uuid,
|
||||
name,
|
||||
status,
|
||||
software_installer_id,
|
||||
vpp_app_team_id
|
||||
)
|
||||
SELECT host_uuid, name, status, software_installer_id, vpp_app_team_id FROM (
|
||||
%s
|
||||
) AS combined
|
||||
ORDER BY sort_name ASC, COALESCE(software_installer_id, vpp_app_team_id, 0)`, strings.Join(softwareUnionParts, " UNION ALL "))
|
||||
}
|
||||
|
||||
stmtSetupScripts := `
|
||||
|
|
@ -198,37 +231,15 @@ WHERE global_or_team_id = ?`
|
|||
return ctxerr.Wrap(ctx, err, "removing stale setup experience entries")
|
||||
}
|
||||
|
||||
// Software installers
|
||||
fleetPlatform := fleet.PlatformFromHost(hostPlatformLike)
|
||||
args := []any{hostUUID, teamID, fleetPlatform, hostPlatformLike, hostPlatformLike}
|
||||
if resetFailedSetupSteps {
|
||||
args = append(args, hostUUID)
|
||||
}
|
||||
if fleetPlatform != "ios" && fleetPlatform != "ipados" {
|
||||
res, err := tx.ExecContext(ctx, stmtSoftwareInstallers, args...)
|
||||
// Combined software (installers + VPP apps)
|
||||
if stmtSoftwareCombined != "" {
|
||||
res, err := tx.ExecContext(ctx, stmtSoftwareCombined, softwareArgs...)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "inserting setup experience software installers")
|
||||
return ctxerr.Wrap(ctx, err, "inserting setup experience software items")
|
||||
}
|
||||
inserts, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "retrieving number of inserted software installers")
|
||||
}
|
||||
totalInsertions += uint(inserts) // nolint: gosec
|
||||
}
|
||||
|
||||
// VPP apps
|
||||
if fleetPlatform == "darwin" || fleetPlatform == "ios" || fleetPlatform == "ipados" {
|
||||
args := []any{hostUUID, teamID, fleetPlatform}
|
||||
if resetFailedSetupSteps {
|
||||
args = append(args, hostUUID)
|
||||
}
|
||||
res, err := tx.ExecContext(ctx, stmtVPPApps, args...)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "inserting setup experience vpp apps")
|
||||
}
|
||||
inserts, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "retrieving number of inserted vpp apps")
|
||||
return ctxerr.Wrap(ctx, err, "retrieving number of inserted software items")
|
||||
}
|
||||
totalInsertions += uint(inserts) // nolint: gosec
|
||||
}
|
||||
|
|
@ -531,7 +542,7 @@ func questionMarks(number int) string {
|
|||
return strings.Join(slices.Repeat([]string{"?"}, number), ",")
|
||||
}
|
||||
|
||||
func (ds *Datastore) ListSetupExperienceResultsByHostUUID(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
func (ds *Datastore) ListSetupExperienceResultsByHostUUID(ctx context.Context, hostUUID string, teamID uint) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
const stmt = `
|
||||
SELECT
|
||||
sesr.id,
|
||||
|
|
@ -571,6 +582,7 @@ LEFT JOIN host_script_results hsr ON hsr.execution_id = sesr.script_execution_id
|
|||
LEFT JOIN vpp_apps_teams vat ON vat.id = sesr.vpp_app_team_id
|
||||
LEFT JOIN vpp_apps va ON vat.adam_id = va.adam_id AND vat.platform = va.platform
|
||||
WHERE host_uuid = ?
|
||||
ORDER BY sesr.id
|
||||
`
|
||||
var results []*fleet.SetupExperienceStatusResult
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &results, stmt, hostUUID); err != nil {
|
||||
|
|
@ -588,28 +600,12 @@ WHERE host_uuid = ?
|
|||
|
||||
// load custom display name and custom icon for the software installers, if any
|
||||
if len(titleIDs) > 0 {
|
||||
// NOTE: as documented in fleet.HostUUIDForSetupExperience, the setup experience "host_uuid"
|
||||
// is NOT always the host.uuid (on Windows and Linux, specifically). So if the host's team is
|
||||
// not found, we simply don't load the icons and display names, anyway we only need those
|
||||
// on macOS currently as it's the only place where the setup experience UI is shown.
|
||||
|
||||
// we need the host's team to load the custom icons and display names
|
||||
const hostTeam = `SELECT team_id FROM hosts WHERE uuid = ? LIMIT 1`
|
||||
var hostTeamID sql.Null[uint]
|
||||
if err := sqlx.GetContext(ctx, ds.reader(ctx), &hostTeamID, hostTeam, hostUUID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// host not found, skip loading icons and display names
|
||||
return results, nil
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "get host team ID for setup experience results")
|
||||
}
|
||||
|
||||
icons, err := ds.GetSoftwareIconsByTeamAndTitleIds(ctx, hostTeamID.V, titleIDs)
|
||||
icons, err := ds.GetSoftwareIconsByTeamAndTitleIds(ctx, teamID, titleIDs)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get software icons by team and title IDs")
|
||||
}
|
||||
|
||||
displayNames, err := ds.getDisplayNamesByTeamAndTitleIds(ctx, hostTeamID.V, titleIDs)
|
||||
displayNames, err := ds.getDisplayNamesByTeamAndTitleIds(ctx, teamID, titleIDs)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get software display names by team and title IDs")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ func TestSetupExperience(t *testing.T) {
|
|||
{"TestGetSetupExperienceScriptByID", testGetSetupExperienceScriptByID},
|
||||
{"TestUpdateSetupExperienceScriptWhileEnqueued", testUpdateSetupExperienceScriptWhileEnqueued},
|
||||
{"TestEnqueueSetupExperienceItemsWindows", testEnqueueSetupExperienceItemsWindows},
|
||||
{"EnqueueSetupExperienceItemsWithDisplayName", testEnqueueSetupExperienceItemsWithDisplayName},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
|
@ -666,6 +667,271 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
|
|||
}
|
||||
}
|
||||
|
||||
// testEnqueueSetupExperienceItemsWithDisplayName verifies that when a custom
|
||||
// display name is set for a software title, the enqueue function uses it to
|
||||
// determine the alphabetical install order (instead of the default
|
||||
// software_titles.name). This ordering also orders the steps in the
|
||||
// setup experience UI. The UI uses the display name if it is set, and
|
||||
// the name if not.
|
||||
func testEnqueueSetupExperienceItemsWithDisplayName(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
test.CreateInsertGlobalVPPToken(t, ds)
|
||||
|
||||
team, err := ds.NewTeam(ctx, &fleet.Team{Name: "team_display_name_test"})
|
||||
require.NoError(t, err)
|
||||
|
||||
user := test.NewUser(t, ds, "DisplayNameUser", "displaynameuser@example.com", true)
|
||||
|
||||
// Create two software installers with titles that sort in a known order:
|
||||
// "AAA_Software" < "ZZZ_Software" (alphabetically)
|
||||
// We will then assign custom display names that invert this order:
|
||||
// "AAA_Software" → "Zulu Custom"
|
||||
// "ZZZ_Software" → "Alpha Custom"
|
||||
// After enqueue, the rows ordered by id (insert order) should reflect
|
||||
// the display-name alphabetical order:
|
||||
// id=N → ZZZ_Software (display name "Alpha Custom", sorts first)
|
||||
// id=N+1 → AAA_Software (display name "Zulu Custom", sorts second)
|
||||
// But the `name` column still stores the original st.name.
|
||||
// Note that the setup experience UI will also follow this ordering;
|
||||
// it will display "Alpha Custom" and then "Zulu Custom".
|
||||
|
||||
tfr1, err := fleet.NewTempFileReader(strings.NewReader("hello1"), t.TempDir)
|
||||
require.NoError(t, err)
|
||||
installerID1, titleID1, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install1",
|
||||
UninstallScript: "uninstall1",
|
||||
InstallerFile: tfr1,
|
||||
StorageID: "storage_dn_1",
|
||||
Filename: "file_dn_1",
|
||||
Title: "AAA_Software",
|
||||
Version: "1.0",
|
||||
Source: "apps",
|
||||
UserID: user.ID,
|
||||
TeamID: &team.ID,
|
||||
Platform: string(fleet.MacOSPlatform),
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tfr2, err := fleet.NewTempFileReader(strings.NewReader("hello2"), t.TempDir)
|
||||
require.NoError(t, err)
|
||||
installerID2, titleID2, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install2",
|
||||
UninstallScript: "uninstall2",
|
||||
InstallerFile: tfr2,
|
||||
StorageID: "storage_dn_2",
|
||||
Filename: "file_dn_2",
|
||||
Title: "ZZZ_Software",
|
||||
Version: "2.0",
|
||||
Source: "apps",
|
||||
UserID: user.ID,
|
||||
TeamID: &team.ID,
|
||||
Platform: string(fleet.MacOSPlatform),
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Mark both installers for setup experience
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, "UPDATE software_installers SET install_during_setup = 1 WHERE id IN (?, ?)", installerID1, installerID2)
|
||||
return err
|
||||
})
|
||||
|
||||
// Set custom display names that invert the alphabetical order
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
if err := updateSoftwareTitleDisplayName(ctx, q, &team.ID, titleID1, "Zulu Custom"); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSoftwareTitleDisplayName(ctx, q, &team.ID, titleID2, "Alpha Custom")
|
||||
})
|
||||
|
||||
// Create two VPP apps with titles that sort in a known order, then invert with display names.
|
||||
vppApp1 := &fleet.VPPApp{
|
||||
Name: "AAA_VPP_App",
|
||||
BundleIdentifier: "com.aaa.vpp",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "dn_adam_1", Platform: fleet.MacOSPlatform}},
|
||||
}
|
||||
vpp1, err := ds.InsertVPPAppWithTeam(ctx, vppApp1, &team.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
vppApp2 := &fleet.VPPApp{
|
||||
Name: "ZZZ_VPP_App",
|
||||
BundleIdentifier: "com.zzz.vpp",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "dn_adam_2", Platform: fleet.MacOSPlatform}},
|
||||
}
|
||||
vpp2, err := ds.InsertVPPAppWithTeam(ctx, vppApp2, &team.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Mark both VPP apps for setup experience
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, "UPDATE vpp_apps_teams SET install_during_setup = 1 WHERE adam_id IN (?, ?)", vpp1.AdamID, vpp2.AdamID)
|
||||
return err
|
||||
})
|
||||
|
||||
// Set custom display names for VPP apps (invert order)
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
if err := updateSoftwareTitleDisplayName(ctx, q, &team.ID, vppApp1.TitleID, "Zulu VPP Custom"); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSoftwareTitleDisplayName(ctx, q, &team.ID, vppApp2.TitleID, "Alpha VPP Custom")
|
||||
})
|
||||
|
||||
// Create a host assigned to the team and enqueue setup experience.
|
||||
// The host must be on the team so that ListSetupExperienceResultsByHostUUID
|
||||
// can look up the team's display names.
|
||||
hostUUID := "host-display-name-test-" + uuid.NewString()
|
||||
host1, err := ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "macos-dn-test",
|
||||
OsqueryHostID: ptr.String("osquery-dn-test"),
|
||||
NodeKey: ptr.String("node-key-dn-test"),
|
||||
UUID: hostUUID,
|
||||
Platform: "darwin",
|
||||
HardwareSerial: "dn-serial-1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team.ID, []uint{host1.ID}))
|
||||
require.NoError(t, err)
|
||||
|
||||
anythingEnqueued, err := ds.EnqueueSetupExperienceItems(ctx, "darwin", "darwin", hostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, anythingEnqueued)
|
||||
|
||||
// --- Verify all rows are globally ordered by display name ---
|
||||
// enqueueSetupExperienceItems inserts software (installers and VPP apps)
|
||||
// together in a single query ordered by COALESCE(display_name, st.name),
|
||||
// so the auto-incremented id reflects the global display-name order.
|
||||
// ListSetupExperienceResultsByHostUUID returns rows ordered by sesr.id,
|
||||
// preserving that insert order. Scripts are inserted last.
|
||||
//
|
||||
// Expected order (all software globally sorted by display name):
|
||||
// 0. ZZZ_Software (installer, display name "Alpha Custom")
|
||||
// 1. ZZZ_VPP_App (VPP app, display name "Alpha VPP Custom")
|
||||
// 2. AAA_Software (installer, display name "Zulu Custom")
|
||||
// 3. AAA_VPP_App (VPP app, display name "Zulu VPP Custom")
|
||||
allResults, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, allResults, 4, "expected 4 results total (2 installers + 2 VPP apps)")
|
||||
|
||||
assert.Equal(t, "ZZZ_Software", allResults[0].Name, "row 0: ZZZ_Software (display name 'Alpha Custom')")
|
||||
assert.Equal(t, "Alpha Custom", allResults[0].DisplayName, "row 0: display name should be 'Alpha Custom'")
|
||||
assert.NotNil(t, allResults[0].SoftwareInstallerID, "row 0: should be a software installer")
|
||||
|
||||
assert.Equal(t, "ZZZ_VPP_App", allResults[1].Name, "row 1: ZZZ_VPP_App (display name 'Alpha VPP Custom')")
|
||||
assert.Equal(t, "Alpha VPP Custom", allResults[1].DisplayName, "row 1: display name should be 'Alpha VPP Custom'")
|
||||
assert.NotNil(t, allResults[1].VPPAppTeamID, "row 1: should be a VPP app")
|
||||
assert.Less(t, allResults[0].ID, allResults[1].ID)
|
||||
|
||||
assert.Equal(t, "AAA_Software", allResults[2].Name, "row 2: AAA_Software (display name 'Zulu Custom')")
|
||||
assert.Equal(t, "Zulu Custom", allResults[2].DisplayName, "row 2: display name should be 'Zulu Custom'")
|
||||
assert.NotNil(t, allResults[2].SoftwareInstallerID, "row 2: should be a software installer")
|
||||
assert.Less(t, allResults[1].ID, allResults[2].ID)
|
||||
|
||||
assert.Equal(t, "AAA_VPP_App", allResults[3].Name, "row 3: AAA_VPP_App (display name 'Zulu VPP Custom')")
|
||||
assert.Equal(t, "Zulu VPP Custom", allResults[3].DisplayName, "row 3: display name should be 'Zulu VPP Custom'")
|
||||
assert.NotNil(t, allResults[3].VPPAppTeamID, "row 3: should be a VPP app")
|
||||
|
||||
// --- Verify fallback: no display name → order uses st.name ---
|
||||
// Add a third installer and a third VPP app, both without custom display
|
||||
// names, then re-enqueue for a new host and verify the globally
|
||||
// interleaved order. Items without a display name fall back to st.name.
|
||||
tfr3, err := fleet.NewTempFileReader(strings.NewReader("hello3"), t.TempDir)
|
||||
require.NoError(t, err)
|
||||
_, _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install3",
|
||||
UninstallScript: "uninstall3",
|
||||
InstallerFile: tfr3,
|
||||
StorageID: "storage_dn_3",
|
||||
Filename: "file_dn_3",
|
||||
Title: "MMM_NoDisplayName",
|
||||
Version: "3.0",
|
||||
Source: "apps",
|
||||
UserID: user.ID,
|
||||
TeamID: &team.ID,
|
||||
Platform: string(fleet.MacOSPlatform),
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, "UPDATE software_installers SET install_during_setup = 1 WHERE id NOT IN (?, ?)", installerID1, installerID2)
|
||||
return err
|
||||
})
|
||||
|
||||
vppApp3 := &fleet.VPPApp{
|
||||
Name: "MMM_VPP_NoDisplayName",
|
||||
BundleIdentifier: "com.mmm.vpp",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "dn_adam_3", Platform: fleet.MacOSPlatform}},
|
||||
}
|
||||
vpp3, err := ds.InsertVPPAppWithTeam(ctx, vppApp3, &team.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, "UPDATE vpp_apps_teams SET install_during_setup = 1 WHERE adam_id = ?", vpp3.AdamID)
|
||||
return err
|
||||
})
|
||||
|
||||
// Re-enqueue for a new host (also on the team) to pick up all installers and VPP apps.
|
||||
hostUUID2 := "host-display-name-fallback-" + uuid.NewString()
|
||||
host2, err := ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "macos-dn-test-2",
|
||||
OsqueryHostID: ptr.String("osquery-dn-test-2"),
|
||||
NodeKey: ptr.String("node-key-dn-test-2"),
|
||||
UUID: hostUUID2,
|
||||
Platform: "darwin",
|
||||
HardwareSerial: "dn-serial-2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team.ID, []uint{host2.ID}))
|
||||
require.NoError(t, err)
|
||||
|
||||
anythingEnqueued, err = ds.EnqueueSetupExperienceItems(ctx, "darwin", "darwin", hostUUID2, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, anythingEnqueued)
|
||||
|
||||
// Verify the globally interleaved order across installers and VPP apps.
|
||||
// The combined INSERT in enqueueSetupExperienceItems orders by
|
||||
// COALESCE(display_name, st.name), and ListSetupExperienceResultsByHostUUID
|
||||
// returns rows ordered by sesr.id (i.e. insert order).
|
||||
//
|
||||
// Expected global order (sorted by COALESCE(display_name, st.name)):
|
||||
// 0. ZZZ_Software (installer, display name "Alpha Custom")
|
||||
// 1. ZZZ_VPP_App (VPP app, display name "Alpha VPP Custom")
|
||||
// 2. MMM_NoDisplayName (installer, no display name → falls back to st.name)
|
||||
// 3. MMM_VPP_NoDisplayName (VPP app, no display name → falls back to st.name)
|
||||
// 4. AAA_Software (installer, display name "Zulu Custom")
|
||||
// 5. AAA_VPP_App (VPP app, display name "Zulu VPP Custom")
|
||||
fallbackResults, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID2, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fallbackResults, 6, "expected 6 results total (3 installers + 3 VPP apps)")
|
||||
|
||||
assert.Equal(t, "ZZZ_Software", fallbackResults[0].Name, "row 0: ZZZ_Software (display name 'Alpha Custom')")
|
||||
assert.Equal(t, "Alpha Custom", fallbackResults[0].DisplayName)
|
||||
assert.NotNil(t, fallbackResults[0].SoftwareInstallerID)
|
||||
|
||||
assert.Equal(t, "ZZZ_VPP_App", fallbackResults[1].Name, "row 1: ZZZ_VPP_App (display name 'Alpha VPP Custom')")
|
||||
assert.Equal(t, "Alpha VPP Custom", fallbackResults[1].DisplayName)
|
||||
assert.NotNil(t, fallbackResults[1].VPPAppTeamID)
|
||||
assert.Less(t, fallbackResults[0].ID, fallbackResults[1].ID)
|
||||
|
||||
assert.Equal(t, "MMM_NoDisplayName", fallbackResults[2].Name, "row 2: MMM_NoDisplayName (no display name, falls back to st.name)")
|
||||
assert.Empty(t, fallbackResults[2].DisplayName)
|
||||
assert.NotNil(t, fallbackResults[2].SoftwareInstallerID)
|
||||
assert.Less(t, fallbackResults[1].ID, fallbackResults[2].ID)
|
||||
|
||||
assert.Equal(t, "MMM_VPP_NoDisplayName", fallbackResults[3].Name, "row 3: MMM_VPP_NoDisplayName (no display name, falls back to st.name)")
|
||||
assert.Empty(t, fallbackResults[3].DisplayName)
|
||||
assert.NotNil(t, fallbackResults[3].VPPAppTeamID)
|
||||
|
||||
assert.Equal(t, "AAA_Software", fallbackResults[4].Name, "row 4: AAA_Software (display name 'Zulu Custom')")
|
||||
assert.Equal(t, "Zulu Custom", fallbackResults[4].DisplayName)
|
||||
assert.NotNil(t, fallbackResults[4].SoftwareInstallerID)
|
||||
|
||||
assert.Equal(t, "AAA_VPP_App", fallbackResults[5].Name, "row 5: AAA_VPP_App (display name 'Zulu VPP Custom')")
|
||||
assert.Equal(t, "Zulu VPP Custom", fallbackResults[5].DisplayName)
|
||||
assert.NotNil(t, fallbackResults[5].VPPAppTeamID)
|
||||
assert.Less(t, fallbackResults[4].ID, fallbackResults[5].ID)
|
||||
}
|
||||
|
||||
type setupExperienceInsertTestRows struct {
|
||||
HostUUID string `db:"host_uuid"`
|
||||
Name string `db:"name"`
|
||||
|
|
@ -1242,7 +1508,7 @@ func testSetupExperienceStatusResults(t *testing.T, ds *Datastore) {
|
|||
insertSetupExperienceStatusResult(r)
|
||||
}
|
||||
|
||||
res, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID)
|
||||
res, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 3)
|
||||
for i, s := range expRes {
|
||||
|
|
@ -1439,14 +1705,14 @@ func testUpdateSetupExperienceScriptWhileEnqueued(t *testing.T, ds *Datastore) {
|
|||
require.NoError(t, err)
|
||||
require.True(t, anythingEnqueued)
|
||||
|
||||
host1OriginalItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID)
|
||||
host1OriginalItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID, team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, host1OriginalItems, 1)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, host1OriginalItems[0].Status)
|
||||
require.NotNil(t, host1OriginalItems[0].SetupExperienceScriptID)
|
||||
require.Equal(t, team1OriginalScript.ID, *host1OriginalItems[0].SetupExperienceScriptID)
|
||||
|
||||
host2OriginalItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam2UUID)
|
||||
host2OriginalItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam2UUID, team2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, host2OriginalItems, 1)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, host2OriginalItems[0].Status)
|
||||
|
|
@ -1463,13 +1729,13 @@ func testUpdateSetupExperienceScriptWhileEnqueued(t *testing.T, ds *Datastore) {
|
|||
require.Equal(t, team1OriginalScript.ScriptContentID, team1UpdatedScript.ScriptContentID)
|
||||
require.Equal(t, team1OriginalScript.ID, team1UpdatedScript.ID)
|
||||
|
||||
host1NewItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID)
|
||||
host1NewItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID, team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, host1NewItems, 1)
|
||||
require.Equal(t, team1OriginalScript.ID, *host1NewItems[0].SetupExperienceScriptID)
|
||||
|
||||
// Should not have perturbed Host 2's enqueued execution either
|
||||
host2NewItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam2UUID)
|
||||
host2NewItems, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam2UUID, team2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, host2NewItems, 1)
|
||||
require.Equal(t, team2OriginalScript.ID, *host2NewItems[0].SetupExperienceScriptID)
|
||||
|
|
@ -1484,12 +1750,12 @@ func testUpdateSetupExperienceScriptWhileEnqueued(t *testing.T, ds *Datastore) {
|
|||
require.NotEqual(t, team1OriginalScript.ScriptContentID, team1UpdatedScript.ScriptContentID)
|
||||
require.NotEqual(t, team1OriginalScript.ID, team1UpdatedScript.ID)
|
||||
|
||||
host1NewItems, err = ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID)
|
||||
host1NewItems, err = ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID, team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, host1NewItems, 0)
|
||||
|
||||
// Should not have affected host 2's enqueued execution
|
||||
host2NewItems, err = ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam2UUID)
|
||||
host2NewItems, err = ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam2UUID, team2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, host2NewItems, 1)
|
||||
require.Equal(t, team2OriginalScript.ID, *host2NewItems[0].SetupExperienceScriptID)
|
||||
|
|
@ -1499,7 +1765,7 @@ func testUpdateSetupExperienceScriptWhileEnqueued(t *testing.T, ds *Datastore) {
|
|||
require.NoError(t, err)
|
||||
require.True(t, anythingEnqueued)
|
||||
|
||||
host1NewItems, err = ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID)
|
||||
host1NewItems, err = ds.ListSetupExperienceResultsByHostUUID(ctx, hostTeam1UUID, team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, host1NewItems, 1)
|
||||
require.Equal(t, team1UpdatedScript.ID, *host1NewItems[0].SetupExperienceScriptID)
|
||||
|
|
|
|||
|
|
@ -1750,7 +1750,7 @@ func testBatchSetSoftwareInstallersSetupExperienceSideEffects(t *testing.T, ds *
|
|||
_, err = ds.EnqueueSetupExperienceItems(ctx, "darwin", "darwin", host1.UUID, *host1.TeamID)
|
||||
require.NoError(t, err)
|
||||
|
||||
statuses, err := ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID)
|
||||
statuses, err := ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, statuses, 2)
|
||||
|
||||
|
|
@ -1800,7 +1800,7 @@ func testBatchSetSoftwareInstallersSetupExperienceSideEffects(t *testing.T, ds *
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
statuses, err = ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID)
|
||||
statuses, err = ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, statuses, 2)
|
||||
|
||||
|
|
@ -1845,7 +1845,7 @@ func testBatchSetSoftwareInstallersSetupExperienceSideEffects(t *testing.T, ds *
|
|||
|
||||
require.NoError(t, err)
|
||||
|
||||
statuses, err = ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID)
|
||||
statuses, err = ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, statuses, 2)
|
||||
|
||||
|
|
@ -1917,7 +1917,7 @@ func testBatchSetSoftwareInstallersSetupExperienceSideEffects(t *testing.T, ds *
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
statuses, err = ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID)
|
||||
statuses, err = ds.ListSetupExperienceResultsByHostUUID(ctx, host1.UUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, statuses, 2)
|
||||
|
||||
|
|
|
|||
|
|
@ -2405,7 +2405,7 @@ type Datastore interface {
|
|||
TeamIDsWithSetupExperienceIdPEnabled(ctx context.Context) ([]uint, error)
|
||||
|
||||
// ListSetupExperienceResultsByHostUUID lists the setup experience results for a host by its UUID.
|
||||
ListSetupExperienceResultsByHostUUID(ctx context.Context, hostUUID string) ([]*SetupExperienceStatusResult, error)
|
||||
ListSetupExperienceResultsByHostUUID(ctx context.Context, hostUUID string, teamID uint) ([]*SetupExperienceStatusResult, error)
|
||||
|
||||
// UpdateSetupExperienceStatusResult updates the given setup experience status result.
|
||||
UpdateSetupExperienceStatusResult(ctx context.Context, status *SetupExperienceStatusResult) error
|
||||
|
|
|
|||
|
|
@ -1531,7 +1531,7 @@ type GetTeamsWithInstallerByHashFunc func(ctx context.Context, sha256 string, ur
|
|||
|
||||
type TeamIDsWithSetupExperienceIdPEnabledFunc func(ctx context.Context) ([]uint, error)
|
||||
|
||||
type ListSetupExperienceResultsByHostUUIDFunc func(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error)
|
||||
type ListSetupExperienceResultsByHostUUIDFunc func(ctx context.Context, hostUUID string, teamID uint) ([]*fleet.SetupExperienceStatusResult, error)
|
||||
|
||||
type UpdateSetupExperienceStatusResultFunc func(ctx context.Context, status *fleet.SetupExperienceStatusResult) error
|
||||
|
||||
|
|
@ -9885,11 +9885,11 @@ func (s *DataStore) TeamIDsWithSetupExperienceIdPEnabled(ctx context.Context) ([
|
|||
return s.TeamIDsWithSetupExperienceIdPEnabledFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *DataStore) ListSetupExperienceResultsByHostUUID(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
func (s *DataStore) ListSetupExperienceResultsByHostUUID(ctx context.Context, hostUUID string, teamID uint) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
s.mu.Lock()
|
||||
s.ListSetupExperienceResultsByHostUUIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.ListSetupExperienceResultsByHostUUIDFunc(ctx, hostUUID)
|
||||
return s.ListSetupExperienceResultsByHostUUIDFunc(ctx, hostUUID, teamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) UpdateSetupExperienceStatusResult(ctx context.Context, status *fleet.SetupExperienceStatusResult) error {
|
||||
|
|
|
|||
|
|
@ -22896,7 +22896,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
// so pull it out manually
|
||||
ubuntuHostUUID, err := fleet.HostUUIDForSetupExperience(ubuntuHost)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -22905,10 +22905,10 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["vim"])
|
||||
require.Empty(t, executionIDs["vim"]) // hasn't run yet due to alphanumeric ordering
|
||||
require.NotEmpty(t, executionIDs["test.tar.gz"])
|
||||
|
||||
// Record a result for vim.
|
||||
// Record a result for test.tar.gz.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
"orbit_node_key": %q,
|
||||
|
|
@ -22937,6 +22937,16 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
require.Equal(t, "vim", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "running", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
|
||||
// get execution ID for vim
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
|
||||
// Record a result for vim
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
|
|
@ -23021,7 +23031,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
// so pull it out manually
|
||||
fedoraHostUUID, err := fleet.HostUUIDForSetupExperience(fedoraHost)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, fedoraHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, fedoraHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -23031,9 +23041,9 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["ruby"])
|
||||
require.NotEmpty(t, executionIDs["test.tar.gz"])
|
||||
require.Empty(t, executionIDs["test.tar.gz"]) // hasn't run yet due to alphanumeric ordering
|
||||
|
||||
// Record a result for vim.
|
||||
// Record a result for ruby.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
"orbit_node_key": %q,
|
||||
|
|
@ -23062,6 +23072,16 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
require.Equal(t, "test.tar.gz", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "running", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
|
||||
// get exec ID for test.tar.gz
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, fedoraHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
|
||||
// Record a result for test.tar.gz.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
|
|
@ -23130,7 +23150,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
// so pull it out manually
|
||||
ubuntuHostUUID, err := fleet.HostUUIDForSetupExperience(ubuntuHost)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -23139,7 +23159,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["vim"])
|
||||
require.Empty(t, executionIDs["vim"]) // hasn't run yet due to alphanumeric ordering
|
||||
require.NotEmpty(t, executionIDs["test.tar.gz"])
|
||||
|
||||
// Cancel the software install for test.tar.gz.
|
||||
|
|
@ -23158,9 +23178,19 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
return getDeviceStatusResponse.Results.Software[i].Name < getDeviceStatusResponse.Results.Software[j].Name
|
||||
})
|
||||
require.Equal(t, "test.tar.gz", getDeviceStatusResponse.Results.Software[0].Name)
|
||||
require.EqualValues(t, "failure", getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, "vim", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "running", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, getDeviceStatusResponse.Results.Software[1].Status)
|
||||
|
||||
// Get execution ID for vim
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
|
||||
// Record a result for vim.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
|
|
@ -23187,9 +23217,9 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
return getDeviceStatusResponse.Results.Software[i].Name < getDeviceStatusResponse.Results.Software[j].Name
|
||||
})
|
||||
require.Equal(t, "test.tar.gz", getDeviceStatusResponse.Results.Software[0].Name)
|
||||
require.EqualValues(t, "failure", getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, "vim", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "success", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, getDeviceStatusResponse.Results.Software[1].Status)
|
||||
})
|
||||
|
||||
t.Run("ubuntu-software-edited-during-se", func(t *testing.T) {
|
||||
|
|
@ -23224,7 +23254,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
// so pull it out manually
|
||||
ubuntuHostUUID, err := fleet.HostUUIDForSetupExperience(ubuntuHost)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -23233,13 +23263,39 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["vim"])
|
||||
require.Empty(t, executionIDs["vim"]) // hasn't run yet due to alphanumeric ordering
|
||||
require.NotEmpty(t, executionIDs["test.tar.gz"])
|
||||
|
||||
// Record a result for test.tar.gz.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
"orbit_node_key": %q,
|
||||
"install_uuid": %q,
|
||||
"install_script_exit_code": 0,
|
||||
"install_script_output": "ok"
|
||||
}`,
|
||||
*ubuntuHost.OrbitNodeKey,
|
||||
executionIDs["test.tar.gz"],
|
||||
),
|
||||
), http.StatusNoContent)
|
||||
|
||||
// Get status of the "Setup experience" for the Ubuntu host.
|
||||
getDeviceStatusResponse = getDeviceSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/device/fleet-desktop-token-ubuntu/setup_experience/status",
|
||||
getDeviceSetupExperienceStatusRequest{}, http.StatusOK, &getDeviceStatusResponse,
|
||||
)
|
||||
require.NoError(t, getDeviceStatusResponse.Err)
|
||||
require.NotNil(t, getDeviceStatusResponse.Results)
|
||||
require.Len(t, getDeviceStatusResponse.Results.Software, 2)
|
||||
require.Equal(t, "test.tar.gz", getDeviceStatusResponse.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, "vim", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, getDeviceStatusResponse.Results.Software[1].Status)
|
||||
|
||||
// Modify the vim installer, which should cause the setup experience item to fail.
|
||||
// update should succeed
|
||||
s.updateSoftwareInstaller(t, &fleet.UpdateSoftwareInstallerPayload{
|
||||
SelfService: ptr.Bool(true),
|
||||
SelfService: new(true),
|
||||
InstallScript: ptr.String("some updated install script"),
|
||||
PreInstallQuery: ptr.String("some new pre install query"),
|
||||
PostInstallScript: ptr.String("some new post install script"),
|
||||
|
|
@ -23248,7 +23304,6 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
TeamID: &team.ID,
|
||||
}, http.StatusOK, "")
|
||||
|
||||
// Get status of the "Setup experience" for the Ubuntu host.
|
||||
getDeviceStatusResponse = getDeviceSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/device/fleet-desktop-token-ubuntu/setup_experience/status",
|
||||
getDeviceSetupExperienceStatusRequest{}, http.StatusOK, &getDeviceStatusResponse,
|
||||
|
|
@ -23260,9 +23315,21 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
return getDeviceStatusResponse.Results.Software[i].Name < getDeviceStatusResponse.Results.Software[j].Name
|
||||
})
|
||||
require.Equal(t, "test.tar.gz", getDeviceStatusResponse.Results.Software[0].Name)
|
||||
require.EqualValues(t, "running", getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, "vim", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "failure", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, getDeviceStatusResponse.Results.Software[1].Status)
|
||||
|
||||
// Get execution ID for vim
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["vim"])
|
||||
require.NotEmpty(t, executionIDs["test.tar.gz"])
|
||||
|
||||
s.lastActivityOfTypeMatches(fleet.ActivityTypeInstalledSoftware{}.ActivityName(), fmt.Sprintf(`{
|
||||
"host_id": %d,
|
||||
|
|
@ -23303,9 +23370,9 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
return getDeviceStatusResponse.Results.Software[i].Name < getDeviceStatusResponse.Results.Software[j].Name
|
||||
})
|
||||
require.Equal(t, "test.tar.gz", getDeviceStatusResponse.Results.Software[0].Name)
|
||||
require.EqualValues(t, "success", getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, getDeviceStatusResponse.Results.Software[0].Status)
|
||||
require.Equal(t, "vim", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "failure", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, getDeviceStatusResponse.Results.Software[1].Status)
|
||||
})
|
||||
|
||||
// Transfer the Ubuntu host to "No team".
|
||||
|
|
@ -23378,7 +23445,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftware()
|
|||
// so pull it out manually
|
||||
ubuntuHostUUID, err := fleet.HostUUIDForSetupExperience(ubuntuHost)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 1)
|
||||
require.NotNil(t, results[0].HostSoftwareInstallsExecutionID)
|
||||
|
|
@ -23512,7 +23579,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftwareWit
|
|||
// so pull it out manually
|
||||
ubuntuHostUUID, err := fleet.HostUUIDForSetupExperience(ubuntuHost)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -23521,10 +23588,10 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftwareWit
|
|||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["vim"])
|
||||
require.Empty(t, executionIDs["vim"]) // hasn't run yet due to alphanumeric ordering
|
||||
require.NotEmpty(t, executionIDs["test.tar.gz"])
|
||||
|
||||
// Record a result for vim.
|
||||
// Record a result for test.tar.gz.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
"orbit_node_key": %q,
|
||||
|
|
@ -23553,6 +23620,16 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceLinuxWithSoftwareWit
|
|||
require.Equal(t, "vim", orbitRes.Results.Software[1].Name)
|
||||
require.EqualValues(t, "running", orbitRes.Results.Software[1].Status)
|
||||
|
||||
// Get execution ID for vim
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
|
||||
// Record a result for vim.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
|
|
@ -23756,7 +23833,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftware(
|
|||
// so pull it out manually
|
||||
windowsHostUUID, err := fleet.HostUUIDForSetupExperience(windowsHost1)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -23766,7 +23843,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftware(
|
|||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["Fleet osquery"])
|
||||
require.NotEmpty(t, executionIDs["Hello world"])
|
||||
require.Empty(t, executionIDs["Hello world"]) // hasn't run yet due to alphanumeric ordering
|
||||
|
||||
// Record a result for Fleet osquery.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
|
|
@ -23797,6 +23874,18 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftware(
|
|||
require.Equal(t, "Hello world", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "running", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
|
||||
// Get execution ID for Hello world
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["Fleet osquery"])
|
||||
require.NotEmpty(t, executionIDs["Hello world"])
|
||||
|
||||
// Record a result for Hello world.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
|
|
@ -23975,7 +24064,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftware(
|
|||
// so pull it out manually
|
||||
windowsHostUUID, err := fleet.HostUUIDForSetupExperience(windowsHost2)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -23985,7 +24074,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftware(
|
|||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["Fleet osquery"])
|
||||
require.NotEmpty(t, executionIDs["Hello world"])
|
||||
require.Empty(t, executionIDs["Hello world"]) // hasn't run yet due to alphanumeric ordering
|
||||
|
||||
// Record a failing result for Fleet osquery.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
|
|
@ -24016,6 +24105,18 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftware(
|
|||
require.Equal(t, "Hello world", getDeviceStatusResponse.Results.Software[1].Name)
|
||||
require.EqualValues(t, "running", getDeviceStatusResponse.Results.Software[1].Status)
|
||||
|
||||
// Get execution ID for Hello world
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["Fleet osquery"])
|
||||
require.NotEmpty(t, executionIDs["Hello world"])
|
||||
|
||||
// Record a result for Hello world.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
fmt.Sprintf(`{
|
||||
|
|
@ -24176,9 +24277,9 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftwareW
|
|||
|
||||
// The setup_experience/status endpoint doesn't return the various IDs for executions,
|
||||
// so pull it out manually
|
||||
ubuntuHostUUID, err := fleet.HostUUIDForSetupExperience(windowsHost)
|
||||
windowsHostUUID, err := fleet.HostUUIDForSetupExperience(windowsHost)
|
||||
require.NoError(t, err)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, ubuntuHostUUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
executionIDs := make(map[string]string) // installer name -> install execution ID
|
||||
|
|
@ -24187,8 +24288,9 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftwareW
|
|||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, executionIDs["Fleet osquery"])
|
||||
require.NotEmpty(t, executionIDs["Hello world"])
|
||||
|
||||
require.NotEmpty(t, executionIDs["Fleet osquery"]) // has started running because it's first alphabetically
|
||||
require.Empty(t, executionIDs["Hello world"]) // not running yet
|
||||
|
||||
// Record a result for Fleet osquery.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(
|
||||
|
|
@ -24203,7 +24305,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftwareW
|
|||
),
|
||||
), http.StatusNoContent)
|
||||
|
||||
// Again get status of the "Setup experience" for the Windos host.
|
||||
// Again get status of the "Setup experience" for the Windows host.
|
||||
orbitRes = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status",
|
||||
fleet.GetOrbitSetupExperienceStatusRequest{OrbitNodeKey: *windowsHost.OrbitNodeKey}, http.StatusOK, &orbitRes,
|
||||
|
|
@ -24211,9 +24313,16 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftwareW
|
|||
require.NotNil(t, orbitRes.Results)
|
||||
require.NotNil(t, orbitRes.Results)
|
||||
require.Len(t, orbitRes.Results.Software, 2)
|
||||
sort.Slice(orbitRes.Results.Software, func(i, j int) bool {
|
||||
return orbitRes.Results.Software[i].Name < orbitRes.Results.Software[j].Name
|
||||
})
|
||||
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, windowsHostUUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
if result.HostSoftwareInstallsExecutionID != nil {
|
||||
executionIDs[result.Name] = *result.HostSoftwareInstallsExecutionID
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, "Fleet osquery", orbitRes.Results.Software[0].Name)
|
||||
require.EqualValues(t, "success", orbitRes.Results.Software[0].Status)
|
||||
require.Equal(t, "Hello world", orbitRes.Results.Software[1].Name)
|
||||
|
|
@ -24232,7 +24341,7 @@ func (s *integrationEnterpriseTestSuite) TestSetupExperienceWindowsWithSoftwareW
|
|||
),
|
||||
), http.StatusNoContent)
|
||||
|
||||
// One last time get status of the "Setup experience" for the Ubuntu host.
|
||||
// One last time get status of the "Setup experience" for the Windows host.
|
||||
orbitRes = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status",
|
||||
fleet.GetOrbitSetupExperienceStatusRequest{OrbitNodeKey: *windowsHost.OrbitNodeKey}, http.StatusOK, &orbitRes,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -365,7 +366,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
|||
|
||||
// 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)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, *enrolledHost.TeamID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
var installUUID string
|
||||
|
|
@ -471,7 +472,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAu
|
|||
require.Equal(t, fleet.SetupExperienceStatusRunning, statusResp.Results.Script.Status)
|
||||
|
||||
// Get script exec ID
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID)
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, *enrolledHost.TeamID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
var execID string
|
||||
|
|
@ -836,7 +837,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithFMAAndVersionRollba
|
|||
require.Equal(t, fmaTitleID, *fmaResult.SoftwareTitleID)
|
||||
|
||||
// Pull the execution ID out of the DB (the status endpoint doesn't surface it).
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, tm.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 1)
|
||||
require.NotNil(t, results[0].HostSoftwareInstallsExecutionID)
|
||||
|
|
@ -1235,29 +1236,53 @@ func (s *integrationMDMTestSuite) TestSetupExperienceVPPInstallError() {
|
|||
}
|
||||
require.ElementsMatch(t, []string{"N1", "Fleetd configuration", "Fleet root certificate authority (CA)"}, profNames)
|
||||
require.ElementsMatch(t, []fleet.MDMDeliveryStatus{fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerifying}, profStatuses)
|
||||
|
||||
// the software and script are still pending
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
require.Len(t, statusResp.Results.Software, 2)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[0].Status)
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[1].Status)
|
||||
|
||||
// Get status: the VPP app install should have run and failed.
|
||||
statusResp = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Nil(t, statusResp.Results.BootstrapPackage) // no bootstrap package involved
|
||||
require.Nil(t, statusResp.Results.AccountConfiguration) // no SSO involved
|
||||
require.Len(t, statusResp.Results.ConfigurationProfiles, 3) // fleetd config, root CA, custom profile
|
||||
require.Len(t, statusResp.Results.Software, 2)
|
||||
// App 5 has no licenses available, so we should get a status failed here...
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, statusResp.Results.Software[0].Status)
|
||||
// ...but setup experience should still continue with the next app in the list
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[1].Status)
|
||||
// Script goes last
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
|
||||
// The status for DummyApp should be "running" now, since it's started installing
|
||||
// but we haven't sent back an installation status from orbit yet
|
||||
statusResp = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Len(t, statusResp.Results.Software, 2)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, statusResp.Results.Software[1].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)
|
||||
// exec ID for "DummyApp" out manually
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 3)
|
||||
var installUUID string
|
||||
for _, r := range results {
|
||||
if r.HostSoftwareInstallsExecutionID != nil &&
|
||||
r.SoftwareInstallerID != nil &&
|
||||
r.Name == statusResp.Results.Software[0].Name {
|
||||
r.Name == statusResp.Results.Software[1].Name {
|
||||
installUUID = *r.HostSoftwareInstallsExecutionID
|
||||
break
|
||||
}
|
||||
|
|
@ -1267,7 +1292,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceVPPInstallError() {
|
|||
|
||||
// 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))
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", *statusResp.Results.Software[1].SoftwareTitleID), nil, http.StatusOK, &getSoftwareTitleResp, "team_id", fmt.Sprintf("%d", *enrolledHost.TeamID))
|
||||
require.NotNil(t, getSoftwareTitleResp.SoftwareTitle)
|
||||
require.NotNil(t, getSoftwareTitleResp.SoftwareTitle.SoftwarePackage)
|
||||
|
||||
|
|
@ -1280,37 +1305,21 @@ func (s *integrationMDMTestSuite) TestSetupExperienceVPPInstallError() {
|
|||
"install_script_output": "ok"
|
||||
}`, *enrolledHost.OrbitNodeKey, installUUID)), http.StatusNoContent)
|
||||
|
||||
// Script is already running, poll again to confirm status
|
||||
statusResp = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Nil(t, statusResp.Results.BootstrapPackage) // no bootstrap package involved
|
||||
require.Nil(t, statusResp.Results.AccountConfiguration) // no SSO involved
|
||||
require.Len(t, statusResp.Results.ConfigurationProfiles, 3) // fleetd config, root CA, custom profile
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
require.Len(t, statusResp.Results.Software, 2)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
// App 5 has no licenses available, so we should get a status failed here and setup experience
|
||||
// should continue
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, statusResp.Results.Software[1].Status)
|
||||
|
||||
// Software installations are done, now we should run the script
|
||||
statusResp = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, 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)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, statusResp.Results.Software[1].Status)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[1].Status)
|
||||
|
||||
// Get script exec ID
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID)
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 3)
|
||||
var execID string
|
||||
|
|
@ -1330,12 +1339,12 @@ func (s *integrationMDMTestSuite) TestSetupExperienceVPPInstallError() {
|
|||
// release of the device, as all setup experience steps are now complete.
|
||||
statusResp = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, statusResp.Results.Software[0].Status)
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, statusResp.Results.Software[1].Status)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[1].Status)
|
||||
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
|
|
@ -1436,7 +1445,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowUpdateScript() {
|
|||
|
||||
// The /setup_experience/status endpoint doesn't return the various IDs for executions, so pull
|
||||
// it out manually (for now only the software install has its execution id)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID, tm.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
|
||||
|
|
@ -1527,7 +1536,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowUpdateScript() {
|
|||
|
||||
// The /setup_experience/status endpoint doesn't return the various IDs for executions, so pull
|
||||
// them out manually
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID)
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID, tm.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 1)
|
||||
var installUUIDs []string
|
||||
|
|
@ -1634,7 +1643,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowCancelScript() {
|
|||
|
||||
// The /setup_experience/status endpoint doesn't return the various IDs for executions, so pull
|
||||
// it out manually (for now only the software install has its execution id)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID, *host.TeamID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
|
||||
|
|
@ -1681,7 +1690,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowCancelScript() {
|
|||
|
||||
// The /setup_experience/status endpoint doesn't return the various IDs for executions, so pull
|
||||
// it out manually (this time get the script exec ID)
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID)
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, host.UUID, *host.TeamID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
|
||||
|
|
@ -1878,6 +1887,10 @@ func (s *integrationMDMTestSuite) TestSetupExperienceWithLotsOfVPPApps() {
|
|||
|
||||
expectedApps := []*fleet.VPPApp{macOSApp1, macOSApp2, macOSApp3, macOSApp4, macOSApp5, macOSApp6}
|
||||
|
||||
slices.SortFunc(expectedApps, func(a, b *fleet.VPPApp) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
expectedAppsByName := map[string]*fleet.VPPApp{
|
||||
macOSApp1.Name: macOSApp1,
|
||||
macOSApp2.Name: macOSApp2,
|
||||
|
|
@ -2144,18 +2157,18 @@ func (s *integrationMDMTestSuite) TestSetupExperienceWithLotsOfVPPApps() {
|
|||
if software.Name == macOSApp1.Name {
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, software.Status)
|
||||
} else {
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, software.Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, software.Status)
|
||||
}
|
||||
require.NotNil(t, software.SoftwareTitleID)
|
||||
require.NotZero(t, *software.SoftwareTitleID)
|
||||
}
|
||||
|
||||
// All apps should have an install record at this point
|
||||
// Only 2 apps have an installation attempt at this point
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
var count int
|
||||
err := sqlx.GetContext(context.Background(), q, &count, "SELECT COUNT(*) FROM host_vpp_software_installs")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 6, count)
|
||||
require.Equal(t, 2, count)
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
@ -2163,7 +2176,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceWithLotsOfVPPApps() {
|
|||
macOSApp1.Name: {},
|
||||
}
|
||||
|
||||
for _, app := range expectedApps {
|
||||
for _, app := range expectedApps[1:] {
|
||||
opts.softwareResultList = append(opts.softwareResultList, fleet.Software{
|
||||
Name: app.Name,
|
||||
BundleIdentifier: app.BundleIdentifier,
|
||||
|
|
@ -2186,9 +2199,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceWithLotsOfVPPApps() {
|
|||
require.True(t, ok)
|
||||
_, shouldBeInstalled := installedApps[software.Name]
|
||||
if shouldBeInstalled {
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, software.Status, software.Name, software.Status)
|
||||
} else {
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, software.Status)
|
||||
require.Equalf(t, fleet.SetupExperienceStatusSuccess, software.Status, "software %s should have succeeded", software.Name)
|
||||
}
|
||||
require.NotNil(t, software.SoftwareTitleID)
|
||||
require.NotZero(t, *software.SoftwareTitleID)
|
||||
|
|
@ -3269,7 +3280,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequireSoftware() {
|
|||
|
||||
// The /setup_experience/status endpoint doesn't return the various IDs for executions, so pull
|
||||
// them out manually
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, *enrolledHost.TeamID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 4)
|
||||
var installUUIDs []string
|
||||
|
|
@ -3278,7 +3289,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequireSoftware() {
|
|||
installUUIDs = append(installUUIDs, *r.HostSoftwareInstallsExecutionID)
|
||||
}
|
||||
}
|
||||
require.Equal(t, len(installUUIDs), 3)
|
||||
require.Len(t, installUUIDs, 1)
|
||||
|
||||
// debugPrintActivities := func(activities []*fleet.UpcomingActivity) []string {
|
||||
// var res []string
|
||||
|
|
@ -3320,10 +3331,14 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequireSoftware() {
|
|||
// Since no results have been recorded, this shouldn't change any database state.
|
||||
statusResp = fleet.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
|
||||
// First software is now running, other software and script are still pending
|
||||
require.Equal(t, len(statusResp.Results.Software), 3)
|
||||
for _, softwareResult := range statusResp.Results.Software {
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, softwareResult.Status)
|
||||
for i, softwareResult := range statusResp.Results.Software {
|
||||
if i == 0 {
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, softwareResult.Status)
|
||||
continue
|
||||
}
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, softwareResult.Status)
|
||||
}
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
|
|
@ -3338,6 +3353,17 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequireSoftware() {
|
|||
"install_script_output": "ok"
|
||||
}`, *enrolledHost.OrbitNodeKey, installUUIDs[0])), http.StatusNoContent)
|
||||
|
||||
results, err = s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, *enrolledHost.TeamID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 4)
|
||||
installUUIDs = []string{}
|
||||
for _, r := range results {
|
||||
if r.HostSoftwareInstallsExecutionID != nil {
|
||||
installUUIDs = append(installUUIDs, *r.HostSoftwareInstallsExecutionID)
|
||||
}
|
||||
}
|
||||
require.Len(t, installUUIDs, 2)
|
||||
|
||||
// status still shows script as pending
|
||||
statusResp = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
|
|
@ -3350,9 +3376,8 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequireSoftware() {
|
|||
require.Len(t, statusResp.Results.Software, 3)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
// Other two software should still be "running"
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, statusResp.Results.Software[1].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, statusResp.Results.Software[2].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[2].Status)
|
||||
|
||||
// Record a failure for the second software.
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result",
|
||||
|
|
@ -3458,9 +3483,28 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequiredSoftwareVPP
|
|||
// Add the app with 1 licenses available
|
||||
s.Do("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: &team.ID, AppStoreID: "4", SelfService: true}, http.StatusOK)
|
||||
|
||||
// Add the VPP app to setup experience
|
||||
// Set custom display names on the VPP apps so they sort alphabetically
|
||||
// after DummyApp, with the no-license app sorting first among the VPP
|
||||
// apps. This ensures the installer (DummyApp) installs first, then the
|
||||
// VPP app with no license fails immediately, triggering the
|
||||
// "require all software" cancel-on-failure flow after a successful
|
||||
// installer install.
|
||||
// Alphabetical order by display name: DummyApp < VPP AAA No License < VPP ZZZ Has License
|
||||
//
|
||||
// This also exercises the fix for #41741: setup experience ordering
|
||||
// should use the custom display name when set.
|
||||
vppTitleID := getSoftwareTitleID(t, s.ds, "App 5", "apps")
|
||||
vppTitleID2 := getSoftwareTitleID(t, s.ds, "App 4", "apps")
|
||||
|
||||
var updateAppResp updateAppStoreAppResponse
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", vppTitleID),
|
||||
&updateAppStoreAppRequest{TeamID: &team.ID, DisplayName: ptr.String("VPP ZZZ Has License")},
|
||||
http.StatusOK, &updateAppResp)
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", vppTitleID2),
|
||||
&updateAppStoreAppRequest{TeamID: &team.ID, DisplayName: ptr.String("VPP AAA No License")},
|
||||
http.StatusOK, &updateAppResp)
|
||||
|
||||
// Add the VPP apps to setup experience
|
||||
installerTitleID := getSoftwareTitleID(t, s.ds, "DummyApp", "apps")
|
||||
var swInstallResp putSetupExperienceSoftwareResponse
|
||||
s.DoJSON("PUT", "/api/v1/fleet/setup_experience/software", putSetupExperienceSoftwareRequest{TeamID: team.ID, TitleIDs: []uint{vppTitleID, vppTitleID2, installerTitleID}}, http.StatusOK, &swInstallResp)
|
||||
|
|
@ -3562,18 +3606,30 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequiredSoftwareVPP
|
|||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
require.Len(t, statusResp.Results.Software, 3)
|
||||
// Ordered by display name: DummyApp (no display name), App 4 (display name
|
||||
// "VPP AAA No License"), App 5 (display name "VPP ZZZ Has License").
|
||||
// Note: Name stores the original st.name, ordering uses display name.
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[0].Status)
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.Equal(t, "App 4", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, "VPP AAA No License", statusResp.Results.Software[1].DisplayName)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[1].Status)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[2].Name)
|
||||
require.Equal(t, "VPP ZZZ Has License", statusResp.Results.Software[2].DisplayName)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[2].Status)
|
||||
|
||||
// call /status endpoint again, DummyApp (first by display name) should be running
|
||||
statusResp = fleet.GetOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Len(t, statusResp.Results.Software, 3)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name, "DummyApp should be first by display name")
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, statusResp.Results.Software[0].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)
|
||||
results, err := s.ds.ListSetupExperienceResultsByHostUUID(ctx, enrolledHost.UUID, team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 4)
|
||||
var installUUID string
|
||||
|
|
@ -3614,11 +3670,15 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequiredSoftwareVPP
|
|||
require.Len(t, statusResp.Results.Software, 3)
|
||||
require.Equal(t, "DummyApp", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
// App 4 has no licenses available, so it should fail and because we have "requre_all_software_macos" set,
|
||||
// the other software and the script should be marked as failed too.
|
||||
// App 4 (display name "VPP AAA No License") has no licenses available, so
|
||||
// it should fail immediately. Because we have "require_all_software_macos"
|
||||
// set, the remaining software (App 5) and the script should be marked as
|
||||
// failed too.
|
||||
require.Equal(t, "App 4", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, "VPP AAA No License", statusResp.Results.Software[1].DisplayName)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, statusResp.Results.Software[1].Status)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[2].Name)
|
||||
require.Equal(t, "VPP ZZZ Has License", statusResp.Results.Software[2].DisplayName)
|
||||
require.Equal(t, fleet.SetupExperienceStatusFailure, statusResp.Results.Software[2].Status)
|
||||
|
||||
// Reset the setup experience items.
|
||||
|
|
@ -3634,8 +3694,10 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithRequiredSoftwareVPP
|
|||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.Equal(t, "App 4", statusResp.Results.Software[1].Name)
|
||||
require.Equal(t, "VPP AAA No License", statusResp.Results.Software[1].DisplayName)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[1].Status)
|
||||
require.Equal(t, "App 5", statusResp.Results.Software[2].Name)
|
||||
require.Equal(t, "VPP ZZZ Has License", statusResp.Results.Software[2].DisplayName)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[2].Status)
|
||||
}
|
||||
|
||||
|
|
@ -3872,7 +3934,9 @@ func (s *integrationMDMTestSuite) TestSetupExperienceMacOSCustomDisplayNameIcon(
|
|||
require.NoError(t, res.Body.Close())
|
||||
require.NoError(t, deviceResp.Err)
|
||||
|
||||
// the software is now running (because previous call to /status kickstarted the process), and script is pending
|
||||
// Software is installed one at a time in alphabetical order by display
|
||||
// name (falling back to name). The previous call to /status kickstarted
|
||||
// the first item; the second remains pending until the first finishes.
|
||||
require.Len(t, deviceResp.Results.Scripts, 1)
|
||||
require.Equal(t, "script.sh", deviceResp.Results.Scripts[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, deviceResp.Results.Scripts[0].Status)
|
||||
|
|
@ -3885,7 +3949,7 @@ func (s *integrationMDMTestSuite) TestSetupExperienceMacOSCustomDisplayNameIcon(
|
|||
require.Empty(t, deviceResp.Results.Software[0].IconURL)
|
||||
require.Equal(t, "EchoApp", deviceResp.Results.Software[1].Name)
|
||||
require.Equal(t, "My Custom EchoApp", deviceResp.Results.Software[1].DisplayName)
|
||||
require.Equal(t, fleet.SetupExperienceStatusRunning, deviceResp.Results.Software[1].Status)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, deviceResp.Results.Software[1].Status)
|
||||
require.NotNil(t, deviceResp.Results.Software[1].SoftwareTitleID)
|
||||
require.Equal(t, echoTitleID, *deviceResp.Results.Software[1].SoftwareTitleID)
|
||||
require.NotEmpty(t, deviceResp.Results.Software[1].IconURL)
|
||||
|
|
|
|||
|
|
@ -894,7 +894,7 @@ func (svc *Service) hostIsInSetupExperience(ctx context.Context, host *fleet.Hos
|
|||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "failed to get host's UUID for the setup experience")
|
||||
}
|
||||
inSetupExperience, err := svc.hasSetupExperiencePendingOrRunningItems(ctx, hostUUID)
|
||||
inSetupExperience, err := svc.hasSetupExperiencePendingOrRunningItems(ctx, hostUUID, ptr.ValOrZero(host.TeamID))
|
||||
if err != nil && !fleet.IsNotFound(err) {
|
||||
return false, ctxerr.Wrap(ctx, err, "check setup experience pending or running items")
|
||||
}
|
||||
|
|
@ -904,8 +904,8 @@ func (svc *Service) hostIsInSetupExperience(ctx context.Context, host *fleet.Hos
|
|||
}
|
||||
}
|
||||
|
||||
func (svc *Service) hasSetupExperiencePendingOrRunningItems(ctx context.Context, hostUUID string) (bool, error) {
|
||||
statuses, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID)
|
||||
func (svc *Service) hasSetupExperiencePendingOrRunningItems(ctx context.Context, hostUUID string, teamID uint) (bool, error) {
|
||||
statuses, err := svc.ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID, teamID)
|
||||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "retrieving setup experience results")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1705,7 +1705,7 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
|
|||
}
|
||||
return host, nil
|
||||
}
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string, teamID uint) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -1943,7 +1943,7 @@ func TestDetailQueries(t *testing.T) {
|
|||
ds.GetHostAwaitingConfigurationFunc = func(ctx context.Context, hostuuid string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string, teamID uint) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -2476,7 +2476,7 @@ func TestDistributedQueryResults(t *testing.T) {
|
|||
EnableSoftwareInventory: true,
|
||||
}}, nil
|
||||
}
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string, teamID uint) ([]*fleet.SetupExperienceStatusResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ func maybeCancelPendingSetupExperienceSteps(ctx context.Context, ds fleet.Datast
|
|||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "failed to get host's UUID for the setup experience")
|
||||
}
|
||||
statuses, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID)
|
||||
statuses, err := ds.ListSetupExperienceResultsByHostUUID(ctx, hostUUID, ptr.ValOrZero(host.TeamID))
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "retrieving setup experience status results for next step")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ func (a *AppleMDM) runPostManualEnrollment(ctx context.Context, args appleMDMArg
|
|||
// We shouldn't have any setup experience steps if we're not on a premium license,
|
||||
// but best to check anyway plus it saves some db queries.
|
||||
if license.IsPremium(ctx) {
|
||||
_, err := a.installSetupExperienceVPPAppsOnIosIpadOS(ctx, args.HostUUID)
|
||||
_, err := a.installSetupExperienceVPPAppsOnIosIpadOS(ctx, args.HostUUID, ptr.ValOrZero(args.TeamID))
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "installing setup experience VPP apps on iOS/iPadOS")
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
|
|||
}
|
||||
}
|
||||
} else {
|
||||
commandUUIDs, err := a.installSetupExperienceVPPAppsOnIosIpadOS(ctx, args.HostUUID)
|
||||
commandUUIDs, err := a.installSetupExperienceVPPAppsOnIosIpadOS(ctx, args.HostUUID, ptr.ValOrZero(args.TeamID))
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "installing setup experience VPP apps on iOS/iPadOS")
|
||||
}
|
||||
|
|
@ -482,7 +482,7 @@ func (a *AppleMDM) runPostDEPReleaseDevice(ctx context.Context, args appleMDMArg
|
|||
}
|
||||
|
||||
if !isMacOS(args.Platform) {
|
||||
setupExperienceStatuses, err := a.Datastore.ListSetupExperienceResultsByHostUUID(ctx, args.HostUUID)
|
||||
setupExperienceStatuses, err := a.Datastore.ListSetupExperienceResultsByHostUUID(ctx, args.HostUUID, ptr.ValOrZero(args.TeamID))
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "retrieving setup experience status results for host pending DEP release")
|
||||
}
|
||||
|
|
@ -523,8 +523,8 @@ func (a *AppleMDM) installFleetd(ctx context.Context, hostUUID string) (string,
|
|||
return cmdUUID, nil
|
||||
}
|
||||
|
||||
func (a *AppleMDM) installSetupExperienceVPPAppsOnIosIpadOS(ctx context.Context, hostUUID string) ([]string, error) {
|
||||
statuses, err := a.Datastore.ListSetupExperienceResultsByHostUUID(ctx, hostUUID)
|
||||
func (a *AppleMDM) installSetupExperienceVPPAppsOnIosIpadOS(ctx context.Context, hostUUID string, teamID uint) ([]string, error) {
|
||||
statuses, err := a.Datastore.ListSetupExperienceResultsByHostUUID(ctx, hostUUID, teamID)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "retrieving setup experience status results for next step")
|
||||
}
|
||||
|
|
@ -583,9 +583,6 @@ func (a *AppleMDM) installSetupExperienceVPPAppsOnIosIpadOS(ctx context.Context,
|
|||
|
||||
cmdUUID, err := a.installSoftwareFromVPP(ctx, host, vppApp, true, opts)
|
||||
|
||||
app.NanoCommandUUID = &cmdUUID
|
||||
app.Status = fleet.SetupExperienceStatusRunning
|
||||
|
||||
if err != nil {
|
||||
// if we get an error (e.g. no available licenses) while attempting to enqueue the
|
||||
// install, then we should immediately go to an error state so setup experience
|
||||
|
|
@ -594,6 +591,8 @@ func (a *AppleMDM) installSetupExperienceVPPAppsOnIosIpadOS(ctx context.Context,
|
|||
app.Status = fleet.SetupExperienceStatusFailure
|
||||
app.Error = ptr.String(err.Error())
|
||||
} else {
|
||||
app.NanoCommandUUID = &cmdUUID
|
||||
app.Status = fleet.SetupExperienceStatusRunning
|
||||
commandUUIDs = append(commandUUIDs, cmdUUID)
|
||||
}
|
||||
if err := a.Datastore.UpdateSetupExperienceStatusResult(ctx, app); err != nil {
|
||||
|
|
|
|||
|
|
@ -1108,7 +1108,7 @@ INSERT INTO setup_experience_status_results (
|
|||
}
|
||||
require.ElementsMatch(t, expectedAdamIDs, installedAdamIDs)
|
||||
|
||||
results, err := ds.ListSetupExperienceResultsByHostUUID(ctx, h.UUID)
|
||||
results, err := ds.ListSetupExperienceResultsByHostUUID(ctx, h.UUID, tm.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, len(expectedAppInstalls))
|
||||
for _, result := range results {
|
||||
|
|
@ -1279,7 +1279,7 @@ INSERT INTO setup_experience_status_results (
|
|||
}
|
||||
require.ElementsMatch(t, expectedAdamIDs, installedAdamIDs)
|
||||
|
||||
results, err := ds.ListSetupExperienceResultsByHostUUID(ctx, h.UUID)
|
||||
results, err := ds.ListSetupExperienceResultsByHostUUID(ctx, h.UUID, tm.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, len(expectedAppInstalls))
|
||||
for _, result := range results {
|
||||
|
|
|
|||
Loading…
Reference in a new issue