Skip to content

Commit

Permalink
Unified Queue: implement correct fleet-initiated flag and setup exper…
Browse files Browse the repository at this point in the history
…ience priority (#25448)
  • Loading branch information
mna authored Jan 20, 2025
1 parent 72cf7d3 commit f7e97f7
Show file tree
Hide file tree
Showing 22 changed files with 156 additions and 124 deletions.
12 changes: 8 additions & 4 deletions ee/server/service/setup_experience.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,10 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, hostUUID string
case len(installersPending) > 0:
// enqueue installers
for _, installer := range installersPending {
// TODO(mna): this should be top priority as this is setup exp.
installUUID, err := svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, *installer.SoftwareInstallerID, false, nil)
installUUID, err := svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, *installer.SoftwareInstallerID, fleet.HostSoftwareInstallOptions{
SelfService: false,
ForSetupExperience: true,
})
if err != nil {
return false, ctxerr.Wrap(ctx, err, "queueing setup experience install request")
}
Expand Down Expand Up @@ -208,8 +210,10 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, hostUUID string
},
}

// TODO(mna): setup experience must be higher-priority
cmdUUID, err := svc.installSoftwareFromVPP(ctx, host, vppApp, true, false)
cmdUUID, err := svc.installSoftwareFromVPP(ctx, host, vppApp, true, fleet.HostSoftwareInstallOptions{
SelfService: false,
ForSetupExperience: true,
})
if err != nil {
return false, ctxerr.Wrap(ctx, err, "queueing vpp app installation")
}
Expand Down
2 changes: 1 addition & 1 deletion ee/server/service/setup_experience_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestSetupExperienceNextStep(t *testing.T) {
return mockListHostsLite, nil
}

ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID, softwareInstallerID uint, selfService bool, policyID *uint) (string, error) {
ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID, softwareInstallerID uint, opts fleet.HostSoftwareInstallOptions) (string, error) {
requestedInstalls[hostID] = append(requestedInstalls[hostID], softwareInstallerID)
return "install-uuid", nil
}
Expand Down
92 changes: 52 additions & 40 deletions ee/server/service/software_installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (

"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/authz"
authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
Expand Down Expand Up @@ -1001,17 +1000,19 @@ func (svc *Service) InstallSoftwareTitle(ctx context.Context, hostID uint, softw
return ctxerr.Wrap(ctx, err, "finding VPP app for title")
}

_, err = svc.installSoftwareFromVPP(ctx, host, vppApp, mobileAppleDevice || fleet.AppleDevicePlatform(platform) == fleet.MacOSPlatform, false)
_, err = svc.installSoftwareFromVPP(ctx, host, vppApp, mobileAppleDevice || fleet.AppleDevicePlatform(platform) == fleet.MacOSPlatform, fleet.HostSoftwareInstallOptions{
SelfService: false,
})
return err
}

func (svc *Service) installSoftwareFromVPP(ctx context.Context, host *fleet.Host, vppApp *fleet.VPPApp, appleDevice bool, selfService bool) (string, error) {
func (svc *Service) installSoftwareFromVPP(ctx context.Context, host *fleet.Host, vppApp *fleet.VPPApp, appleDevice bool, opts fleet.HostSoftwareInstallOptions) (string, error) {
token, err := svc.GetVPPTokenIfCanInstallVPPApps(ctx, appleDevice, host)
if err != nil {
return "", err
}

return svc.InstallVPPAppPostValidation(ctx, host, vppApp, token, selfService, nil)
return svc.InstallVPPAppPostValidation(ctx, host, vppApp, token, opts)
}

func (svc *Service) GetVPPTokenIfCanInstallVPPApps(ctx context.Context, appleDevice bool, host *fleet.Host) (string, error) {
Expand Down Expand Up @@ -1057,7 +1058,7 @@ func (svc *Service) GetVPPTokenIfCanInstallVPPApps(ctx context.Context, appleDev
return token, nil
}

func (svc *Service) InstallVPPAppPostValidation(ctx context.Context, host *fleet.Host, vppApp *fleet.VPPApp, token string, selfService bool, policyID *uint) (string, error) {
func (svc *Service) InstallVPPAppPostValidation(ctx context.Context, host *fleet.Host, vppApp *fleet.VPPApp, token string, opts fleet.HostSoftwareInstallOptions) (string, error) {
// at this moment, neither the UI nor the back-end are prepared to
// handle [asyncronous errors][1] on assignment, so before assigning a
// device to a license, we need to:
Expand Down Expand Up @@ -1133,7 +1134,7 @@ func (svc *Service) InstallVPPAppPostValidation(ctx context.Context, host *fleet

// enqueue the VPP app command to install
cmdUUID := uuid.NewString()
err = svc.ds.InsertHostVPPSoftwareInstall(ctx, host.ID, vppApp.VPPAppID, cmdUUID, eventID, selfService, policyID)
err = svc.ds.InsertHostVPPSoftwareInstall(ctx, host.ID, vppApp.VPPAppID, cmdUUID, eventID, opts)
if err != nil {
return "", ctxerr.Wrapf(ctx, err, "inserting host vpp software install for host with serial %s and app with adamID %s", host.HardwareSerial, vppApp.AdamID)
}
Expand All @@ -1159,7 +1160,9 @@ func (svc *Service) installSoftwareTitleUsingInstaller(ctx context.Context, host
}
}

_, err := svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, installer.InstallerID, false, nil)
_, err := svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, installer.InstallerID, fleet.HostSoftwareInstallOptions{
SelfService: false,
})
return ctxerr.Wrap(ctx, err, "inserting software install request")
}

Expand Down Expand Up @@ -1249,40 +1252,45 @@ func (svc *Service) UninstallSoftwareTitle(ctx context.Context, hostID uint, sof
}
}

// Get the uninstall script and use the standard script infrastructure to run it.
contents, err := svc.ds.GetAnyScriptContents(ctx, installer.UninstallScriptContentID)
if err != nil {
if fleet.IsNotFound(err) {
return ctxerr.Wrap(ctx,
fleet.NewInvalidArgumentError("software_title_id", `No uninstall script exists for the provided "software_title_id".`).
WithStatus(http.StatusNotFound), "getting uninstall script contents")
}
return err
}

var teamID uint
if host.TeamID != nil {
teamID = *host.TeamID
}
// create the script execution request; the host will be notified of the
// script execution request via the orbit config's Notifications mechanism.
request := fleet.HostScriptRequestPayload{
HostID: host.ID,
ScriptContents: string(contents),
ScriptContentID: installer.UninstallScriptContentID,
TeamID: teamID,
}
if ctxUser := authz.UserFromContext(ctx); ctxUser != nil {
request.UserID = &ctxUser.ID
}
scriptResult, err := svc.ds.NewInternalScriptExecutionRequest(ctx, &request)
if err != nil {
return ctxerr.Wrap(ctx, err, "create script execution request")
}
// TODO(mna): an uninstall request will first go in the upcoming_activities
// queue, and only when that activity is ready to run will the internal script
// be created in host_script_requests. Keeping it here but commented, for when
// we implement the queue execution.

// // Get the uninstall script and use the standard script infrastructure to run it.
// contents, err := svc.ds.GetAnyScriptContents(ctx, installer.UninstallScriptContentID)
// if err != nil {
// if fleet.IsNotFound(err) {
// return ctxerr.Wrap(ctx,
// fleet.NewInvalidArgumentError("software_title_id", `No uninstall script exists for the provided "software_title_id".`).
// WithStatus(http.StatusNotFound), "getting uninstall script contents")
// }
// return err
// }
// var teamID uint
// if host.TeamID != nil {
// teamID = *host.TeamID
// }
// // create the script execution request; the host will be notified of the
// // script execution request via the orbit config's Notifications mechanism.
// request := fleet.HostScriptRequestPayload{
// HostID: host.ID,
// ScriptContents: string(contents),
// ScriptContentID: installer.UninstallScriptContentID,
// TeamID: teamID,
// }
// if ctxUser := authz.UserFromContext(ctx); ctxUser != nil {
// request.UserID = &ctxUser.ID
// }
// scriptResult, err := svc.ds.NewInternalScriptExecutionRequest(ctx, &request)
// if err != nil {
// return ctxerr.Wrap(ctx, err, "create script execution request")
// }

// Update the host software installs table with the uninstall request.
// Pending uninstalls will automatically show up in the UI Host Details -> Activity -> Upcoming tab.
if err = svc.insertSoftwareUninstallRequest(ctx, scriptResult.ExecutionID, host, installer); err != nil {
execID := uuid.NewString()
if err = svc.insertSoftwareUninstallRequest(ctx, execID, host, installer); err != nil {
return err
}

Expand Down Expand Up @@ -1830,7 +1838,9 @@ func (svc *Service) SelfServiceInstallSoftwareTitle(ctx context.Context, host *f
}
}

_, err = svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, installer.InstallerID, true, nil)
_, err = svc.ds.InsertSoftwareInstallRequest(ctx, host.ID, installer.InstallerID, fleet.HostSoftwareInstallOptions{
SelfService: true,
})
return ctxerr.Wrap(ctx, err, "inserting self-service software install request")
}

Expand Down Expand Up @@ -1864,7 +1874,9 @@ func (svc *Service) SelfServiceInstallSoftwareTitle(ctx context.Context, host *f
platform := host.FleetPlatform()
mobileAppleDevice := fleet.AppleDevicePlatform(platform) == fleet.IOSPlatform || fleet.AppleDevicePlatform(platform) == fleet.IPadOSPlatform

_, err = svc.installSoftwareFromVPP(ctx, host, vppApp, mobileAppleDevice || fleet.AppleDevicePlatform(platform) == fleet.MacOSPlatform, true)
_, err = svc.installSoftwareFromVPP(ctx, host, vppApp, mobileAppleDevice || fleet.AppleDevicePlatform(platform) == fleet.MacOSPlatform, fleet.HostSoftwareInstallOptions{
SelfService: true,
})
return err
}

Expand Down
2 changes: 1 addition & 1 deletion ee/server/service/software_installers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestInstallUninstallAuth(t *testing.T) {
ds.GetHostLastInstallDataFunc = func(ctx context.Context, hostID uint, installerID uint) (*fleet.HostLastInstallData, error) {
return nil, nil
}
ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID uint, softwareInstallerID uint, selfService bool, policyID *uint) (string,
ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID uint, softwareInstallerID uint, opts fleet.HostSoftwareInstallOptions) (string,
error,
) {
return "request_id", nil
Expand Down
13 changes: 3 additions & 10 deletions server/datastore/mysql/activities.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,9 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint

listStmts := []string{
// list pending scripts
// TODO(mna): should the user name IF use fleet_initiated?
`SELECT
ua.execution_id as uuid,
IF(sua.policy_id IS NOT NULL, 'Fleet', COALESCE(u.name, JSON_EXTRACT(ua.payload, '$.user.name'))) as name,
IF(ua.fleet_initiated, 'Fleet', COALESCE(u.name, JSON_EXTRACT(ua.payload, '$.user.name'))) as name,
u.id as user_id,
COALESCE(u.gravatar_url, JSON_EXTRACT(ua.payload, '$.user.gravatar_url')) as gravatar_url,
COALESCE(u.email, JSON_EXTRACT(ua.payload, '$.user.email')) as user_email,
Expand Down Expand Up @@ -307,12 +306,9 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
ua.activity_type = 'script'
`,
// list pending software installs
// TODO(mna): should the user name IF use fleet_initiated?
`SELECT
ua.execution_id as uuid,
-- policies with automatic installers generate a host_software_installs with (user_id=NULL,self_service=0),
-- so we mark those as "Fleet"
IF(ua.user_id IS NULL AND NOT JSON_EXTRACT(ua.payload, '$.self_service'), 'Fleet', COALESCE(u.name, JSON_EXTRACT(ua.payload, '$.user.name'))) AS name,
IF(ua.fleet_initiated, 'Fleet', COALESCE(u.name, JSON_EXTRACT(ua.payload, '$.user.name'))) AS name,
ua.user_id as user_id,
COALESCE(u.gravatar_url, JSON_EXTRACT(ua.payload, '$.user.gravatar_url')) as gravatar_url,
COALESCE(u.email, JSON_EXTRACT(ua.payload, '$.user.email')) as user_email,
Expand Down Expand Up @@ -352,12 +348,9 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
ua.activity_type = 'software_install'
`,
// list pending software uninstalls
// TODO(mna): should the user name IF use fleet_initiated?
`SELECT
ua.execution_id as uuid,
-- policies with automatic installers generate a host_software_installs with (user_id=NULL,self_service=0),
-- so we mark those as "Fleet"
IF(ua.user_id IS NULL, 'Fleet', COALESCE(u.name, JSON_EXTRACT(ua.payload, '$.user.name'))) AS name,
IF(ua.fleet_initiated, 'Fleet', COALESCE(u.name, JSON_EXTRACT(ua.payload, '$.user.name'))) AS name,
ua.user_id as user_id,
COALESCE(u.gravatar_url, JSON_EXTRACT(ua.payload, '$.user.gravatar_url')) as gravatar_url,
COALESCE(u.email, JSON_EXTRACT(ua.payload, '$.user.email')) as user_email,
Expand Down
17 changes: 8 additions & 9 deletions server/datastore/mysql/activities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {

// install the VPP app on h1
// commander, _ := createMDMAppleCommanderAndStorage(t, ds)
err = ds.InsertHostVPPSoftwareInstall(ctx, h1.ID, vppApp.VPPAppID, vppCommand1, "event-id-1", false, nil)
err = ds.InsertHostVPPSoftwareInstall(ctx, h1.ID, vppApp.VPPAppID, vppCommand1, "event-id-1", fleet.HostSoftwareInstallOptions{})
require.NoError(t, err)
// err = commander.EnqueueCommand(
// ctx,
Expand All @@ -466,7 +466,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
// )
// require.NoError(t, err)
// install the VPP app on h2, self-service
err = ds.InsertHostVPPSoftwareInstall(noUserCtx, h2.ID, vppApp.VPPAppID, vppCommand2, "event-id-2", true, nil)
err = ds.InsertHostVPPSoftwareInstall(noUserCtx, h2.ID, vppApp.VPPAppID, vppCommand2, "event-id-2", fleet.HostSoftwareInstallOptions{SelfService: true})
require.NoError(t, err)
// err = commander.EnqueueCommand(
// ctx,
Expand Down Expand Up @@ -516,7 +516,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
// create some software installs requests for h1, make some complete
// h1FooFailed, err := ds.InsertSoftwareInstallRequest(ctx, h1.ID, sw1Meta.InstallerID, false, nil)
// require.NoError(t, err)
h1Bar, err := ds.InsertSoftwareInstallRequest(ctx, h1.ID, sw2Meta.InstallerID, false, nil)
h1Bar, err := ds.InsertSoftwareInstallRequest(ctx, h1.ID, sw2Meta.InstallerID, fleet.HostSoftwareInstallOptions{})
require.NoError(t, err)
t.Log("h1Bar", h1Bar)
// err = ds.SetHostSoftwareInstallResult(ctx, &fleet.HostSoftwareInstallResultPayload{
Expand All @@ -541,7 +541,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
Query: "SELECT 1",
})
require.NoError(t, err)
h1Fleet, err := ds.InsertSoftwareInstallRequest(noUserCtx, h1.ID, sw1Meta.InstallerID, false, &policy.ID)
h1Fleet, err := ds.InsertSoftwareInstallRequest(noUserCtx, h1.ID, sw1Meta.InstallerID, fleet.HostSoftwareInstallOptions{PolicyID: &policy.ID})
t.Log("h1Fleet", h1Fleet)
require.NoError(t, err)

Expand All @@ -557,11 +557,11 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
// require.NoError(t, err)
// h2F := hsr.ExecutionID
// add a pending software install request for h2
h2Bar, err := ds.InsertSoftwareInstallRequest(ctx, h2.ID, sw2Meta.InstallerID, false, nil)
h2Bar, err := ds.InsertSoftwareInstallRequest(ctx, h2.ID, sw2Meta.InstallerID, fleet.HostSoftwareInstallOptions{})
require.NoError(t, err)
t.Log("h2Bar", h2Bar)
// No user for this one and Self-service, means it was installed by the end user, so the user_id should be null/nil.
h2SelfService, err := ds.InsertSoftwareInstallRequest(noUserCtx, h2.ID, sw1Meta.InstallerID, true, nil)
h2SelfService, err := ds.InsertSoftwareInstallRequest(noUserCtx, h2.ID, sw1Meta.InstallerID, fleet.HostSoftwareInstallOptions{SelfService: true})
require.NoError(t, err)
t.Log("h2SelfService", h2SelfService)

Expand All @@ -576,7 +576,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
t.Log("h2SetupExp", h2SetupExp)

// create pending install and uninstall requests for h3 that will be deleted
_, err = ds.InsertSoftwareInstallRequest(ctx, h3.ID, sw3Meta.InstallerID, false, nil)
_, err = ds.InsertSoftwareInstallRequest(ctx, h3.ID, sw3Meta.InstallerID, fleet.HostSoftwareInstallOptions{})
require.NoError(t, err)
err = ds.InsertSoftwareUninstallRequest(ctx, "uninstallRun", h3.ID, sw3Meta.InstallerID)
require.NoError(t, err)
Expand Down Expand Up @@ -764,8 +764,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
require.Equal(t, wantUser.Email, *a.ActorEmail, "result %d", i)
} else {
require.Nil(t, a.ActorID, "result %d", i)
// TODO(mna): this should probably become consistent across activity types, all based on fleet_initiated
if a.Type == (fleet.ActivityInstalledAppStoreApp{}.ActivityName()) {
if a.FleetInitiated {
require.NotNil(t, a.ActorFullName, "result %d", i)
require.Equal(t, "Fleet", *a.ActorFullName, "result %d", i)
} else {
Expand Down
2 changes: 1 addition & 1 deletion server/datastore/mysql/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7030,7 +7030,7 @@ func testHostsDeleteHosts(t *testing.T, ds *Datastore) {
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
_, err = ds.InsertSoftwareInstallRequest(context.Background(), host.ID, softwareInstaller, false, nil)
_, err = ds.InsertSoftwareInstallRequest(context.Background(), host.ID, softwareInstaller, fleet.HostSoftwareInstallOptions{})
require.NoError(t, err)

// Add an awaiting configuration entry
Expand Down
4 changes: 2 additions & 2 deletions server/datastore/mysql/policies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func testGlobalPolicyPendingScriptsAndInstalls(t *testing.T, ds *Datastore) {
require.Len(t, policies, 1)

// create a pending software install request
_, err = ds.InsertSoftwareInstallRequest(ctx, host2.ID, installerID, false, &policy2.ID)
_, err = ds.InsertSoftwareInstallRequest(ctx, host2.ID, installerID, fleet.HostSoftwareInstallOptions{PolicyID: &policy2.ID})
require.NoError(t, err)

pendingInstalls, err := ds.ListPendingSoftwareInstalls(ctx, host2.ID)
Expand Down Expand Up @@ -900,7 +900,7 @@ func testTeamPolicyPendingScriptsAndInstalls(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
// create a pending software install request
_, err = ds.InsertSoftwareInstallRequest(ctx, host2.ID, installerID, false, &policy2.ID)
_, err = ds.InsertSoftwareInstallRequest(ctx, host2.ID, installerID, fleet.HostSoftwareInstallOptions{PolicyID: &policy2.ID})
require.NoError(t, err)

pendingInstalls, err := ds.ListPendingSoftwareInstalls(ctx, host2.ID)
Expand Down
3 changes: 2 additions & 1 deletion server/datastore/mysql/scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (ds *Datastore) NewHostScriptExecutionRequest(ctx context.Context, request
})
}

// TODO(mna): might become unused after unified queue implementation
func (ds *Datastore) NewInternalScriptExecutionRequest(ctx context.Context, request *fleet.HostScriptRequestPayload) (*fleet.HostScriptResult, error) {
var res *fleet.HostScriptResult
var err error
Expand Down Expand Up @@ -109,7 +110,7 @@ WHERE
request.HostID,
priority,
request.UserID,
isInternal || request.UserID == nil, // TODO(mna): confirm if this makes sense
request.PolicyID != nil, // fleet-initiated if request is via a policy failure
execID,
request.SyncRequest,
isInternal,
Expand Down
Loading

0 comments on commit f7e97f7

Please sign in to comment.