Skip to content

API endpoints for Linux setup experience - #32493

Merged
lucasmrod merged 13 commits into
mainfrom
32040-linux-setup-experience-backend
Sep 4, 2025
Merged

API endpoints for Linux setup experience#32493
lucasmrod merged 13 commits into
mainfrom
32040-linux-setup-experience-backend

Conversation

@lucasmrod

@lucasmrod lucasmrod commented Sep 1, 2025

Copy link
Copy Markdown
Member

For #32040.


Backend changes to unblock the development of the orbit and frontend changes.

New GET and PUT APIs for setting/getting software for Linux Setup Experience:

curl -k -X GET -H "Authorization: Bearer $TEST_TOKEN" https://localhost:8080/api/latest/fleet/setup_experience/linux/software?team_id=8&per_page=3000
curl -k -X PUT -H "Authorization: Bearer $TEST_TOKEN" https://localhost:8080/api/latest/fleet/setup_experience/linux/software -d '{"team_id":8,"software_title_ids":[3000, 3001, 3007]}'

New setup_experience/init API called by orbit to trigger the Linux setup experience on the device:

curl -v -k -X POST -H "Content-Type: application/json" "https://localhost:8080/api/fleet/orbit/setup_experience/init" -d '{"orbit_node_key": "ynYEtFsvv9xZ7rX619UE8of1I28H+GCj"}'

Get status API to call on "My device":

curl -v -k -X POST "https://localhost:8080/api/latest/fleet/device/7d940b6e-130a-493b-b58a-2b6e9f9f8bfc/setup_experience/status"

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

Testing

New Fleet configuration settings

  • Verified that the setting is exported via fleetctl generate-gitops
  • Verified the setting is documented in a separate PR to the GitOps documentation
  • Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional)

Summary by CodeRabbit

  • New Features

    • Added Linux support for Setup Experience alongside macOS.
    • Introduced platform-specific admin APIs to configure and retrieve Setup Experience software (macOS/Linux).
    • Added device API to report Setup Experience status and an Orbit API to initialize Setup Experience on non-macOS devices.
    • Setup Experience now gates policy queries on Linux until setup is complete.
    • New activity log entry when Setup Experience software is edited (includes platform and team).
  • Documentation

    • Updated audit logs reference to include the new “edited setup experience software” event.

@codecov

codecov Bot commented Sep 1, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.97376% with 127 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.03%. Comparing base (e6ef600) to head (c0989d7).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
server/datastore/mysql/setup_experience.go 63.85% 19 Missing and 11 partials ⚠️
server/service/orbit.go 52.38% 16 Missing and 4 partials ⚠️
ee/server/service/devices.go 51.61% 10 Missing and 5 partials ⚠️
server/service/osquery.go 57.57% 9 Missing and 5 partials ⚠️
server/service/devices.go 33.33% 10 Missing and 2 partials ⚠️
ee/server/service/setup_experience.go 65.51% 6 Missing and 4 partials ⚠️
ee/server/service/orbit.go 68.96% 6 Missing and 3 partials ⚠️
server/datastore/mysql/activities.go 45.45% 4 Missing and 2 partials ⚠️
cmd/fleetctl/fleetctl/generate_gitops.go 75.00% 2 Missing and 1 partial ⚠️
server/fleet/setup_experience.go 66.66% 2 Missing and 1 partial ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #32493      +/-   ##
==========================================
+ Coverage   64.02%   64.03%   +0.01%     
==========================================
  Files        1987     1986       -1     
  Lines      195630   195830     +200     
  Branches     6549     6542       -7     
==========================================
+ Hits       125251   125400     +149     
- Misses      60576    60598      +22     
- Partials     9803     9832      +29     
Flag Coverage Δ
backend 65.25% <62.97%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread server/service/handler.go
@lucasmrod

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 3, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Sep 3, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds Linux support to setup_experience: platform-scoped software configuration (macOS/Linux), orbit init endpoint, device status endpoint, platform-aware datastore changes, activity logging, and control-flow updates to operate on Host objects. Includes osquery policy gating during setup, platform_like persistence, and extensive integration and unit tests.

Changes

Cohort / File(s) Summary
API routing & endpoints
server/service/handler.go, server/service/setup_experience.go, server/service/orbit.go, server/service/devices.go, server/service/endpoint_utils.go
Registers new endpoints: PUT/GET /fleet/setup_experience/{platform}/software, POST device /fleet/device/{token}/setup_experience/status, POST orbit /fleet/orbit/setup_experience/init. Adds platform validation/transform, device/orbit handlers, and helper for bad requests.
Enterprise service (EE) logic
ee/server/service/orbit.go, ee/server/service/devices.go, ee/server/service/setup_experience.go
Adds SetupExperienceInit and device status retrieval. Threads platform through software list/set, changes SetupExperienceNextStep to take *Host, updates cancel handling and logging, and filters software results.
Fleet service interfaces & types
server/fleet/service.go, server/fleet/setup_experience.go, server/fleet/orbit.go, server/fleet/activities.go
Updates Service/EnterpriseOverrides signatures to accept platform and *Host. Adds SetupExperienceInitResult, DeviceSetupExperienceStatusPayload, HostUUIDForSetupExperience, expands platform support check, and introduces ActivityEditedSetupExperienceSoftware.
Datastore interface & mocks
server/fleet/datastore.go, server/mock/datastore_mock.go
Adds platform arg to Set/List SetupExperienceSoftwareTitles and hostPlatformLike to EnqueueSetupExperienceItems. Updates mock function types and methods accordingly.
MySQL datastore impl
server/datastore/mysql/setup_experience.go, server/datastore/mysql/hosts.go
Implements platform-aware enqueueing (darwin/linux with distro-specific filters), platform-scoped software set/list, and persists platform_like on host insert.
MySQL tests
server/datastore/mysql/setup_experience_test.go, server/datastore/mysql/hosts_test.go, server/datastore/mysql/software_installers_test.go
Updates tests to pass platform parameters, validate per-platform behavior, and adjust signatures.
Osquery policy gating
server/service/osquery.go, server/service/osquery_test.go
Generalizes “host in setup experience” to macOS/Linux; adds helper to detect pending/running items and datastore hook for listing results.
Integration tests
server/service/integration_mdm_setup_experience_test.go, server/service/integration_mdm_test.go, server/service/integration_core_test.go
Adds Linux setup experience end-to-end tests, platform endpoint validation tests, updates for macOS-specific endpoints, and minor param rename.
Docs & audit
docs/Contributing/reference/audit-logs.md
Documents new edited_setup_experience_software audit log event; note duplication of the section.
Devices endpoint (oss)
server/service/devices.go
Wires device-scoped status endpoint and service method (currently license-gated).
Minor/formatting
server/datastore/mysql/common_mysql/testing_utils/testing_utils.go
Simplifies TestAddress declaration; no behavior change.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Admin
  participant API as Fleet API
  participant Svc as Service
  participant DS as Datastore
  participant Act as Activities

  Admin->>API: PUT /fleet/setup_experience/{platform}/software (title IDs, team)
  API->>Svc: SetSetupExperienceSoftware(platform, teamID, titles)
  Svc->>DS: SetSetupExperienceSoftwareTitles(platform, teamID, titles)
  DS-->>Svc: ok
  Svc->>Act: Record EditedSetupExperienceSoftware (platform, team)
  Act-->>Svc: ok
  Svc-->>API: 200
  API-->>Admin: Success
Loading
sequenceDiagram
  autonumber
  participant Orbit as Orbit (host)
  participant API as Fleet API
  participant Svc as Service (EE)
  participant DS as Datastore

  Note over Orbit,API: Initialization (non-darwin also)
  Orbit->>API: POST /fleet/orbit/setup_experience/init
  API->>Svc: SetupExperienceInit(ctx)
  Svc->>DS: EnqueueSetupExperienceItems(host.platform_like, hostUUID, teamID)
  DS-->>Svc: enabled?
  Svc-->>API: { enabled }
  API-->>Orbit: { enabled }

  Note over Orbit,API: Status polling
  Orbit->>API: POST /fleet/device/{token}/setup_experience/status
  API->>Svc: GetDeviceSetupExperienceStatus(ctx)
  Svc->>DS: ListSetupExperienceResultsByHostUUID(hostUUID)
  DS-->>Svc: results (software-only)
  Svc->>Svc: SetupExperienceNextStep(host)
  Svc-->>API: { setup_experience_results }

  Note over Orbit,Svc: Result reporting (scripts/software)
  Orbit->>API: Report script/software result
  API->>Svc: SaveHost...Result(host, result)
  Svc->>Svc: SetupExperienceNextStep(host)
  Svc-->>API: ok
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested labels

#g-software, ~assisting g-software

Suggested reviewers

  • lukeheath
  • rachaelshaw
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 32040-linux-setup-experience-backend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
ee/server/service/orbit.go (1)

178-223: Guard against nil pointers and VPP cases when marking cancellations.

r.IsForSoftware() can include entries without SoftwareInstallerID or HostSoftwareInstallsExecutionID (e.g., VPP). The current code dereferences without checks and can panic.

-    if r.IsForSoftware() {
+    if r.IsForSoftware() && r.SoftwareInstallerID != nil && r.HostSoftwareInstallsExecutionID != nil {
       softwarePackage := ""
       installerMeta, err := svc.ds.GetSoftwareInstallerMetadataByID(ctx, *r.SoftwareInstallerID)
       if err != nil && !fleet.IsNotFound(err) {
         return ctxerr.Wrap(ctx, err, "getting software installer metadata for cancelled setup experience software install")
       }
       if installerMeta != nil {
         softwarePackage = installerMeta.Name
       }
       activity := fleet.ActivityTypeInstalledSoftware{
         HostID:          hostID,
         HostDisplayName: hostDisplayName,
         SoftwareTitle:   r.Name,
         SoftwarePackage: softwarePackage,
         InstallUUID:     *r.HostSoftwareInstallsExecutionID,
         Status:          "failed",
         SelfService:     false,
         FromSetupExperience: true,
       }
       err = svc.NewActivity(ctx, nil, activity)
       if err != nil {
         return ctxerr.Wrap(ctx, err, "creating activity for cancelled setup experience software install")
       }
+    } else if r.IsForSoftware() {
+      // Likely a VPP entry or missing IDs. Avoid panic; optionally add a TODO to emit a VPP-specific activity.
+      level.Warn(svc.logger).Log("msg", "skipping activity for cancelled setup experience item with missing installer IDs", "host_uuid", hostUUID, "name", r.Name)
     }
server/service/orbit.go (1)

865-874: Avoid nil panic and use consistent host UUID for setup-experience script results

  • Use HostUUIDForSetupExperience for Linux parity (enqueue/update must match).
  • Guard the enterprise override callback to avoid calling a nil function in OSS builds.

Apply:

-        if updated, err := maybeUpdateSetupExperienceStatus(ctx, svc.ds, fleet.SetupExperienceScriptResult{
-            HostUUID:    host.UUID,
+        hostUUID := fleet.HostUUIDForSetupExperience(host)
+        if updated, err := maybeUpdateSetupExperienceStatus(ctx, svc.ds, fleet.SetupExperienceScriptResult{
+            HostUUID:    hostUUID,
             ExecutionID: result.ExecutionID,
             ExitCode:    result.ExitCode,
         }, true); err != nil {
             return ctxerr.Wrap(ctx, err, "update setup experience status")
         } else if updated {
-            level.Debug(svc.logger).Log("msg", "setup experience script result updated", "host_uuid", host.UUID, "execution_id", result.ExecutionID)
+            level.Debug(svc.logger).Log("msg", "setup experience script result updated", "host_uuid", hostUUID, "execution_id", result.ExecutionID)
             fromSetupExperience = true
-            _, err := svc.EnterpriseOverrides.SetupExperienceNextStep(ctx, host)
-            if err != nil {
-                return ctxerr.Wrap(ctx, err, "getting next step for host setup experience")
-            }
+            if next := svc.EnterpriseOverrides.SetupExperienceNextStep; next != nil {
+                if _, err := next(ctx, host); err != nil {
+                    return ctxerr.Wrap(ctx, err, "getting next step for host setup experience")
+                }
+            }
         }

Also applies to: 874-878

server/fleet/service.go (1)

1227-1247: Implement missing Setup Experience software APIs and mocks

  • Define SetSetupExperienceSoftware and ListSetupExperienceSoftware on the OSS Service (server/fleet/service.go)
  • Add corresponding methods to your mocks (e.g. in server/mock)
  • Wire these methods into EE via EnterpriseOverrides alongside SetupExperienceInit and GetDeviceSetupExperienceStatus
ee/server/service/setup_experience.go (2)

226-244: Don't set NanoCommandUUID when enqueueing VPP app fails

NanoCommandUUID is assigned before checking err. If installSoftwareFromVPP fails, the record may incorrectly show a command UUID. Set the UUID only on success.

Apply:

-            cmdUUID, err := svc.installSoftwareFromVPP(ctx, host, vppApp, true, fleet.HostSoftwareInstallOptions{
+            cmdUUID, err := svc.installSoftwareFromVPP(ctx, host, vppApp, true, fleet.HostSoftwareInstallOptions{
                 SelfService:        false,
                 ForSetupExperience: true,
             })
-
-            app.NanoCommandUUID = &cmdUUID
-            app.Status = fleet.SetupExperienceStatusRunning
-
-            if err != nil {
+            if err == nil {
+                app.NanoCommandUUID = &cmdUUID
+                app.Status = fleet.SetupExperienceStatusRunning
+            } else {
                 // 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.
                 level.Warn(svc.logger).Log("msg", "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())
-            }
+            }

150-156: Guard against nil host

SetupExperienceNextStep dereferences host.ID later. Add a nil check to avoid a panic if callers pass nil.

 func (svc *Service) SetupExperienceNextStep(ctx context.Context, host *fleet.Host) (bool, error) {
+    if host == nil {
+        return false, fleet.NewInvalidArgumentError("host", "host is required")
+    }
     hostUUID := fleet.HostUUIDForSetupExperience(host)
server/datastore/mysql/setup_experience.go (1)

21-60: Linux distro checks: normalize host platform_like to avoid case/variant mismatches

The query compares ? against 'debian'/'rhel'. Normalize hostPlatformLike to lowercase before binding to avoid mismatches (e.g., 'Debian' or 'rhel8').

-        fleetPlatform := fleet.PlatformFromHost(hostPlatformLike)
-        res, err := tx.ExecContext(ctx, stmtSoftwareInstallers, hostUUID, teamID, fleetPlatform, hostPlatformLike, hostPlatformLike)
+        fleetPlatform := fleet.PlatformFromHost(hostPlatformLike)
+        distro := strings.ToLower(hostPlatformLike)
+        res, err := tx.ExecContext(ctx, stmtSoftwareInstallers, hostUUID, teamID, fleetPlatform, distro, distro)

Also applies to: 103-106

♻️ Duplicate comments (1)
server/service/handler.go (1)

386-392: Validate {platform} and document deprecation window.

Ensure the request decoder rejects unknown platforms (allow only "macos" and "linux"). Keep the legacy path but mark as deprecated in docs/UI.

🧹 Nitpick comments (32)
server/service/integration_core_test.go (1)

10310-10314: Optional: normalize common aliases for platform to reduce test footguns

If a test accidentally passes “macos” or “win”, Host.Platform will be set to an unexpected value. Consider normalizing known aliases before assignment (keeps existing callers working and avoids subtle flakiness).

 func createOrbitEnrolledHost(t *testing.T, platform, suffix string, ds fleet.Datastore) *fleet.Host {
   name := t.Name() + suffix
+  // Normalize common aliases used in tests.
+  switch strings.ToLower(platform) {
+  case "macos":
+    platform = "darwin"
+  case "win":
+    platform = "windows"
+  }
   h, err := ds.NewHost(context.Background(), &fleet.Host{
@@
-    Platform:        platform,
+    Platform:        platform,
   })
docs/Contributing/reference/audit-logs.md (1)

2005-2013: Resolve markdownlint warnings (MD001, MD010) in new section.

  • MD001: Change “#### Example” to “### Example”.
  • MD010: Replace hard tabs with spaces inside the JSON example.

Apply this diff:

-#### Example
+### Example

 ```json
 {
-	"platform": "darwin",
-	"team_id": 1,
-	"team_name": "Workstations"
+  "platform": "darwin",
+  "team_id": 1,
+  "team_name": "Workstations"
 }

</blockquote></details>
<details>
<summary>server/fleet/orbit.go (1)</summary><blockquote>

`204-208`: **Tighten doc comment and remove stray blank line.**

Keep the doc comment contiguous and concise to satisfy go doc style; no blank line between the comment and type.

Apply this diff:

```diff
-// SetupExperienceInitResult is the payload returned when the orbit client manually initiates
-// setup experience for non-darwin platforms.
-
-type SetupExperienceInitResult struct {
+// SetupExperienceInitResult is the payload returned when the Orbit client manually
+// initiates the setup experience for non-darwin platforms.
+type SetupExperienceInitResult struct {
  	Enabled bool `json:"enabled"`
 }
server/datastore/mysql/software_installers_test.go (1)

1284-1284: Prefer platform constant over string literal.

Use fleet.MacOSPlatform (or the host’s actual platform) instead of "darwin" to avoid drift.

Apply this diff:

-	_, err = ds.EnqueueSetupExperienceItems(ctx, "darwin", host1.UUID, *host1.TeamID)
+	_, err = ds.EnqueueSetupExperienceItems(ctx, fleet.MacOSPlatform, host1.UUID, *host1.TeamID)
server/service/apple_mdm.go (1)

3497-3504: Platform-aware enqueue looks right—please verify accepted values and consider gating non-macOS.

  • Nice update passing info.Platform. Confirm datastore expects darwin/ios/ipados (not macos). If not, normalize before calling.
  • Optional: skip the DB call for iOS/iPadOS (likely no setup-experience items today) to avoid a no-op round-trip.
-        hasSetupExpItems, err = svc.ds.EnqueueSetupExperienceItems(r.Context, info.Platform, r.ID, info.TeamID)
+        // Optional: only enqueue for macOS; avoid no-op for iOS/iPadOS.
+        if info.Platform == "darwin" {
+            hasSetupExpItems, err = svc.ds.EnqueueSetupExperienceItems(r.Context, info.Platform, r.ID, info.TeamID)
+        }
server/service/integration_mdm_test.go (3)

15904-15907: Use require.NoError for error assertions

Prefer require.NoError(t, err) over require.Nil(t, err) to properly assert error values.

Apply this diff:

-require.Nil(t, respPutSetupExperience.Error())
+require.NoError(t, respPutSetupExperience.Error())

Consider making the same change for the earlier assertion on Line 15899.


15901-15903: Escape team_name in JSON payload to avoid malformed JSON

If team1.Name contains quotes/backslashes, the fmt.Sprintf with "%s" can generate invalid JSON. Use %q or pre-marshal the payload.

Apply this diff:

- s.lastActivityOfTypeMatches(fleet.ActivityEditedSetupExperienceSoftware{}.ActivityName(),
-  fmt.Sprintf(`{"platform": "darwin", "team_id": %d, "team_name": "%s"}`, team1.ID, team1.Name), 0)
+ s.lastActivityOfTypeMatches(fleet.ActivityEditedSetupExperienceSoftware{}.ActivityName(),
+  fmt.Sprintf(`{"platform":"darwin","team_id":%d,"team_name":%q}`, team1.ID, team1.Name), 0)

And similarly for the later assertion:

- s.lastActivityOfTypeMatches(fleet.ActivityEditedSetupExperienceSoftware{}.ActivityName(),
-  fmt.Sprintf(`{"platform": "darwin", "team_id": %d, "team_name": "%s"}`, team1.ID, team1.Name), 0)
+ s.lastActivityOfTypeMatches(fleet.ActivityEditedSetupExperienceSoftware{}.ActivityName(),
+  fmt.Sprintf(`{"platform":"darwin","team_id":%d,"team_name":%q}`, team1.ID, team1.Name), 0)

Also applies to: 15908-15910


15908-15910: Optionally assert both activities exist (latest and previous)

To prove both PUTs emitted an activity, also check offset 1 for the earlier event.

Apply this diff:

 s.lastActivityOfTypeMatches(fleet.ActivityEditedSetupExperienceSoftware{}.ActivityName(),
   fmt.Sprintf(`{"platform":"darwin","team_id":%d,"team_name":%q}`, team1.ID, team1.Name), 0)
+s.lastActivityOfTypeMatches(fleet.ActivityEditedSetupExperienceSoftware{}.ActivityName(),
+  fmt.Sprintf(`{"platform":"darwin","team_id":%d,"team_name":%q}`, team1.ID, team1.Name), 1)
server/fleet/setup_experience.go (2)

188-191: Use platform constant for consistency.

Avoid the raw "darwin" literal; use the same MacOSPlatform constant used elsewhere.

-func IsSetupExperienceSupported(hostPlatform string) bool {
-	return hostPlatform == "darwin" || IsLinux(hostPlatform)
-}
+func IsSetupExperienceSupported(hostPlatform string) bool {
+	return hostPlatform == string(MacOSPlatform) || IsLinux(hostPlatform)
+}

193-198: Decide: omit vs empty array in device payload.

With omitempty, software may be omitted or null; many clients prefer an empty [] for stable contracts. If that’s desired, ensure handlers initialize to make([]*SetupExperienceStatusResult, 0) when empty.

server/datastore/mysql/hosts_test.go (1)

7556-7559: EnqueueSetupExperienceItems now takes platform — verify platform-like semantics.

If this parameter expects a platform-like value (e.g., "linux" not "ubuntu"), passing host.Platform may be incorrect for Linux hosts. Here "darwin" is fine. Consider deriving/using a platform-like helper to avoid mismatch and optionally add a Linux case to cover the new path.

server/service/osquery_test.go (2)

1790-1791: Good addition: OsqueryHostID set for Linux host

Setting OsqueryHostID ensures HostUUIDForSetupExperience can prefer it on Linux. Consider adding a focused unit test that asserts HostUUIDForSetupExperience(host) returns OsqueryHostID when Platform is linux (and falls back to UUID otherwise).


1853-1855: Mock returns no setup-experience results — add a non-empty path test

Right now the mock returns nil results. Add a test exercising a non-empty ListSetupExperienceResultsByHostUUID flow to lock in behavior (e.g., pending item present) and ensure no panics on result processing.

ee/server/service/setup_experience_test.go (1)

78-81: *API change to pass fleet.Host looks good; expand coverage

  • Passing a host object is an improvement. Please:
    • Add a Linux variant (Platform "linux") to verify the same sequencing (installer before script) for Linux setup experience.
    • Assert the script execution payload matches the expected SetupExperienceScriptID and ScriptContentID for the queued script.
    • Re-introduce a negative case (unknown host/UUID) to ensure a stable error path is still returned.

Also applies to: 99-102, 114-117, 136-139, 151-154, 176-179, 193-196, 210-213

server/service/endpoint_utils.go (1)

146-150: Add formatted bad request helper — minor polish

Helper is fine. Optional: add a short doc comment for consistency with badRequest and to aid discovery.

server/service/devices.go (2)

800-809: Drop defensive type assertion; rely on decoder (also remove fmt).

The request decoder ensures type-safety. The manual type-check adds noise and pulls in fmt just for error formatting. Simplify and align with other endpoints.

Apply:

@@
-import (
+import (
 	"context"
 	"crypto/x509"
 	"database/sql"
 	"encoding/json"
 	"errors"
-	"fmt"
 	"io"
 	"net/http"
 	"net/url"
 	"strconv"
 	"time"
@@
-func getDeviceSetupExperienceStatusEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
-	if _, ok := request.(*getDeviceSetupExperienceStatusRequest); !ok {
-		return nil, fmt.Errorf("internal error: invalid request type: %T", request)
-	}
+func getDeviceSetupExperienceStatusEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
+	_ = request.(*getDeviceSetupExperienceStatusRequest) // decoder guarantees type
 	results, err := svc.GetDeviceSetupExperienceStatus(ctx)
 	if err != nil {
-		return &getDeviceSetupExperienceStatusResponse{Err: err}, nil
+		return getDeviceSetupExperienceStatusResponse{Err: err}, nil
 	}
-	return &getDeviceSetupExperienceStatusResponse{Results: results}, nil
+	return getDeviceSetupExperienceStatusResponse{Results: results}, nil
 }

Also applies to: 9-9


811-817: Implement device-scoped logic; don’t skipauth in final version.

When you wire the real implementation, read the host from hostctx and enforce device-auth like other device endpoints; then compute and return the payload. Avoid SkipAuthorization here.

server/service/handler.go (2)

868-871: Expose a GET alias for an idempotent status read.

This endpoint is read-only; consider adding GET alongside POST for consistency with other device “status” reads.

Apply:

 de.WithCustomMiddleware(
   errorLimiter.Limit("setup_experience_status", desktopQuota, logger),
 ).POST("/api/_version_/fleet/device/{token}/setup_experience/status", getDeviceSetupExperienceStatusEndpoint, getDeviceSetupExperienceStatusRequest{})
+de.WithCustomMiddleware(
+  errorLimiter.Limit("setup_experience_status", desktopQuota, logger),
+).GET("/api/_version_/fleet/device/{token}/setup_experience/status", getDeviceSetupExperienceStatusEndpoint, getDeviceSetupExperienceStatusRequest{})

925-925: Rate-limit Orbit init endpoint.

Protect the new Orbit init with the existing error limiter bucket to avoid abuse bursts.

Apply:

-oe.POST("/api/fleet/orbit/setup_experience/init", orbitSetupExperienceInitEndpoint, orbitSetupExperienceInitRequest{})
+oe.WithCustomMiddleware(
+  errorLimiter.Limit("orbit_setup_experience_init", desktopQuota, logger),
+).POST("/api/fleet/orbit/setup_experience/init", orbitSetupExperienceInitEndpoint, orbitSetupExperienceInitRequest{})
server/fleet/datastore.go (2)

2070-2072: Document and validate allowed platform values ("linux" | "macos").

These new methods accept a free-form platform string. Please document the accepted values and ensure implementations validate early to avoid silent no-ops.

Apply this doc-only diff:

-	SetSetupExperienceSoftwareTitles(ctx context.Context, platform string, teamID uint, titleIDs []uint) error
+	// platform must be "linux" or "macos".
+	SetSetupExperienceSoftwareTitles(ctx context.Context, platform string, teamID uint, titleIDs []uint) error
-	ListSetupExperienceSoftwareTitles(ctx context.Context, platform string, teamID uint, opts ListOptions) ([]SoftwareTitleListResult, int, *PaginationMetadata, error)
+	// platform must be "linux" or "macos".
+	ListSetupExperienceSoftwareTitles(ctx context.Context, platform string, teamID uint, opts ListOptions) ([]SoftwareTitleListResult, int, *PaginationMetadata, error)

2101-2107: Prefer typed constants for platform_like values.

Hardcoding "darwin"/"debian"/"rhel" in comments invites drift. Introduce a typed enum or constants (e.g., fleet.PlatformLikeDarwin, PlatformLikeDebian, PlatformLikeRHEL) and reference them across DS and service layers.

server/service/integration_mdm_setup_experience_test.go (3)

2039-2051: Fix misleading comment ("vim" → "emacs").

This block records the result for the emacs install, not vim.

-        // Record a result for vim.
+        // Record a result for emacs.

2060-2061: Remove duplicate assertion.

Back-to-back require.Len on the same slice is redundant.

-        require.Len(t, getDeviceStatusResponse.Results.Software, 1)
-        require.Len(t, getDeviceStatusResponse.Results.Software, 1)
+        require.Len(t, getDeviceStatusResponse.Results.Software, 1)

1901-1912: Fix misleading comment ("vim" → "ruby").

This section updates Ruby’s result.

-        // Record a result for vim.
+        // Record a result for ruby.
ee/server/service/orbit.go (1)

291-316: Handle unknown PlatformLike gracefully in init.

If PlatformLike is empty/unknown, EnqueueSetupExperienceItems may silently noop. Consider logging at info/warn and/or defaulting linux hosts without platform_like to include only generic tgz items.

server/datastore/mysql/setup_experience_test.go (2)

134-157: Add Linux enqueue coverage

Great Darwin coverage. Please add Linux cases to assert:

  • Enqueue returns true when Linux-eligible installers exist.
  • host_mdm_apple_awaiting_configuration is not set for Linux.
  • setup_experience_status_results rows use the expected host UUID mapping.

I can draft a focused test (mirroring team1/team2) that calls EnqueueSetupExperienceItems(ctx, "linux", ...) and asserts no awaiting_configuration rows while verifying pending rows were inserted. Want me to propose it?

Also applies to: 230-245


379-404: Platform-param tests: include Linux List/Set paths

You validated “darwin” and negative “ios”. Consider Linux-specific ListSetupExperienceSoftwareTitles/SetSetupExperienceSoftwareTitles tests (e.g., ensure VPP paths are excluded and only Linux installers are returned/toggled).

Happy to add a minimal Linux test that uploads a .deb/.rpm/.tar.gz and exercises Set→List→Unset.

Also applies to: 539-674

server/datastore/mysql/setup_experience.go (4)

21-60: Simplify installer platform predicate for clarity

The inner OR checks si.platform='darwin' while the outer condition already enforces si.platform=?; this is only true for darwin. Consider splitting the SELECT by platform or using CASE for readability. Behavior is correct but hard to audit.


141-149: Awaiting configuration set for macOS only

This writes to host_mdm_apple_awaiting_configuration only for darwin. If Linux setup items are enqueued, confirm there's an equivalent UX signal for Orbit/frontend. If not, consider a Linux-specific signal/table.


588-652: Two-step SELECT+UPDATE per status: consider single UPDATE with WHERE for fewer round-trips

Current flow reads id then updates. If no other fields are needed, a single UPDATE ... WHERE host_uuid = ? AND =? is simpler and avoids an extra query.


610-631: Add composite indexes for setup_experience_status_results
Existing migrations only define single-column indexes (idx_setup_experience_scripts_host_uuid, idx_setup_experience_scripts_hsi_id, idx_setup_experience_scripts_nano_command_uuid and idx_setup_experience_scripts_script_execution_id). Add composite indexes on:

  • (host_uuid, host_software_installs_execution_id)
  • (host_uuid, nano_command_uuid)
  • (host_uuid, script_execution_id)
    to ensure your UPDATE/SELECT queries use index lookups for both predicates.
server/mock/datastore_mock.go (1)

1320-1320: Consider strong typing for platform arguments.

Using raw string for platform/hostPlatform invites subtle drift (e.g., "linux" vs "Linux"). Consider a shared typed alias and constants (e.g., fleet.Platform) across the interfaces. Non-blocking for this mock file.

Also applies to: 1322-1322, 1336-1336

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b731d3a and 4af85ff.

⛔ Files ignored due to path filters (1)
  • server/service/testdata/software-installers/test.tar.gz is excluded by !**/*.gz
📒 Files selected for processing (29)
  • changes/32040-linux-setup-experience-backend (1 hunks)
  • docs/Contributing/reference/audit-logs.md (1 hunks)
  • ee/server/service/devices.go (1 hunks)
  • ee/server/service/orbit.go (5 hunks)
  • ee/server/service/setup_experience.go (2 hunks)
  • ee/server/service/setup_experience_test.go (8 hunks)
  • server/datastore/mysql/common_mysql/testing_utils/testing_utils.go (1 hunks)
  • server/datastore/mysql/hosts.go (3 hunks)
  • server/datastore/mysql/hosts_test.go (2 hunks)
  • server/datastore/mysql/setup_experience.go (9 hunks)
  • server/datastore/mysql/setup_experience_test.go (10 hunks)
  • server/datastore/mysql/software_installers_test.go (1 hunks)
  • server/fleet/activities.go (2 hunks)
  • server/fleet/datastore.go (2 hunks)
  • server/fleet/orbit.go (1 hunks)
  • server/fleet/service.go (3 hunks)
  • server/fleet/setup_experience.go (1 hunks)
  • server/mock/datastore_mock.go (4 hunks)
  • server/service/apple_mdm.go (1 hunks)
  • server/service/devices.go (2 hunks)
  • server/service/endpoint_utils.go (7 hunks)
  • server/service/handler.go (3 hunks)
  • server/service/integration_core_test.go (2 hunks)
  • server/service/integration_mdm_setup_experience_test.go (11 hunks)
  • server/service/integration_mdm_test.go (1 hunks)
  • server/service/orbit.go (3 hunks)
  • server/service/osquery.go (2 hunks)
  • server/service/osquery_test.go (2 hunks)
  • server/service/setup_experience.go (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • server/datastore/mysql/common_mysql/testing_utils/testing_utils.go
  • server/service/integration_mdm_setup_experience_test.go
  • server/fleet/setup_experience.go
  • server/service/handler.go
  • server/fleet/orbit.go
  • server/service/integration_core_test.go
  • ee/server/service/devices.go
  • server/datastore/mysql/hosts.go
  • server/fleet/activities.go
  • server/service/devices.go
  • server/service/osquery.go
  • server/datastore/mysql/software_installers_test.go
  • server/mock/datastore_mock.go
  • server/service/integration_mdm_test.go
  • server/service/setup_experience.go
  • server/fleet/service.go
  • ee/server/service/orbit.go
  • ee/server/service/setup_experience_test.go
  • server/datastore/mysql/setup_experience_test.go
  • server/service/endpoint_utils.go
  • server/service/orbit.go
  • server/service/apple_mdm.go
  • server/datastore/mysql/hosts_test.go
  • server/fleet/datastore.go
  • server/service/osquery_test.go
  • ee/server/service/setup_experience.go
  • server/datastore/mysql/setup_experience.go
🪛 markdownlint-cli2 (0.17.2)
docs/Contributing/reference/audit-logs.md

2005-2005: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


2009-2009: Hard tabs
Column: 1

(MD010, no-hard-tabs)


2010-2010: Hard tabs
Column: 1

(MD010, no-hard-tabs)


2011-2011: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (24)
server/service/integration_core_test.go (1)

10300-10304: LGTM: helper signature uses platform consistently

Renaming the parameter to platform reads clearer and matches the Host.Platform assignment below. No functional issues spotted.

server/datastore/mysql/common_mysql/testing_utils/testing_utils.go (1)

26-26: LGTM: formatting-only var init kept behavior identical.

No functional change; initialization still occurs at package init and respects FLEET_MYSQL_TEST_PORT.

server/fleet/activities.go (2)

224-226: Add to ActivityDetailsList looks correct.

The new activity is discoverable by the doc generator and APIs that enumerate activities.


2766-2786: Confirm canonical platform and team nullability handling

  • Platform is already normalized via host.FleetPlatform()/PlatformFromHost and covered by the server/fleet/hosts_test.go tests.
  • TeamID/TeamName use zero/empty rather than pointer-null like other activities—verify downstream UI/API treat 0/"" as “no team” or refactor to pointers for consistency.
server/datastore/mysql/hosts.go (1)

92-106: Verify null platform_like in secondary host INSERTs
Secondary INSERTs in server/datastore/mysql/hosts.go (lines 2262, 2369) and in server/datastore/mysql/android.go (line 32) omit platform_like, so it will be null until detail ingestion. Confirm this is intended.

docs/Contributing/reference/audit-logs.md (1)

2001-2004: Confirm “No team” semantics match backend.

You document team_id as 0 and team_name as empty string for “No team.” Please confirm the emitted activity really uses 0/"" for this event (other activities sometimes use null or -1). If the backend differs, update the doc to match.

server/datastore/mysql/hosts_test.go (1)

7300-7301: Explicit platform on host initialization — good change.

Setting Platform ensures the subsequent enqueue call has a concrete value.

server/service/endpoint_utils.go (2)

7-7: fmt import is appropriate

Used by badRequestf; no issues.


99-101: Signature reformatting only

Multi-line wrapping of function signatures changes no behavior. OK to keep.

Also applies to: 108-123, 125-140, 152-176, 178-196, 198-222

server/service/osquery.go (2)

839-858: Platform-aware check looks good.

Clean split (macOS uses awaiting-config, Linux uses result scan). After the change above, this will correctly gate for all item types.


890-897: Good placement for gating policies during setup.

Runs after interval check, logs and skips policies when setup is active.

server/service/integration_mdm_setup_experience_test.go (1)

1609-2064: Strong, end-to-end Linux flow coverage.

Great coverage: platform-scoped config, per-distro enqueueing, orbit init, status transitions, and policy gating all verified.

ee/server/service/orbit.go (1)

291-316: LGTM: orbit-scoped init endpoint.

Auth skip is appropriate; hostctx usage and teamID derivation are correct. Returning a simple Enabled flag keeps the client contract clean.

server/fleet/service.go (1)

38-41: No outdated SetupExperienceNextStep references remain: I found no call sites still passing a string UUID and no implementations using the old signature—this change is fully propagated.

server/service/setup_experience.go (2)

24-27: Platform validation + internal mapping looks solid

Allowing only "", "macos", or "linux" and mapping to "darwin" internally keeps the external API stable while aligning with datastore expectations. Usage at both PUT/GET endpoints is correct.

Also applies to: 36-38, 73-75, 275-287


18-22: Remove this check — {platform} is correctly bound on both PUT (line 389) and GET (line 391) routes in server/service/handler.go.

ee/server/service/setup_experience.go (1)

17-31: Platform normalization is already handled Confirmed that all call sites invoke SetSetupExperienceSoftware with the result of transformPlatformForSetupExperience (mapping “macos”→“darwin”), so no further changes are needed.

server/datastore/mysql/setup_experience.go (4)

158-162: Platform-scoped setters look correct

Validation + per-platform unsets/sets for installers and VPP teams align with new API. Good guardrails to prevent cross-platform assignment.

Also applies to: 246-253, 288-306


316-328: ListTitles platform filter passes through correctly

Passing Platform to ListSoftwareTitles ensures correct scoping. Good.


351-381: Host UUID scoping in SELECT is precise

Query is appropriately scoped by host_uuid; safe against cross-host leakage.


424-452: GetSetupExperienceScript safe without ORDER BY

Unique constraint idx_setup_experience_scripts_global_or_team_id on global_or_team_id guarantees at most one row, so adding ORDER BY is unnecessary.

server/mock/datastore_mock.go (3)

8194-8199: LGTM: forwards platform and follows mock pattern.

Invoked flag set under lock; parameters and return match the function type; delegation is correct.


8201-8206: LGTM: platform-aware list wrapper is correct.

Properly sets the invoked flag and forwards ctx, platform, teamID, and opts.


8250-8255: LGTM: enqueue wrapper propagates hostPlatform.

Locking/flag pattern and argument order match the declared function type.

Comment thread changes/32040-linux-setup-experience-backend
Comment thread ee/server/service/devices.go
Comment thread ee/server/service/orbit.go
Comment thread server/fleet/setup_experience.go
Comment thread server/service/orbit.go
Comment thread server/service/orbit.go
Comment thread server/service/osquery.go
dantecatalfamo
dantecatalfamo previously approved these changes Sep 3, 2025
@dantecatalfamo

Copy link
Copy Markdown
Member

Left one comment about auth failure

@lucasmrod
lucasmrod merged commit 29475ab into main Sep 4, 2025
46 checks passed
@lucasmrod
lucasmrod deleted the 32040-linux-setup-experience-backend branch September 4, 2025 15:58
jacobshandling added a commit that referenced this pull request Sep 4, 2025
## PR 1/2 for #32037 

- Implements update for the Linux setup experience from the IT admin's
point of view. Updates for the end-user ("My device" page) to follow
- Works in concert with the new endpoints implemented in
#32493

- Splits Controls > Setup experience > Install software into 3 tabbed
sections, one for each of macOS, Windows (placeholder state for now, to
be implemented in following iteration), and Linux.
- Dynamically calls new GET and PUT endpoints and routes data
accordingly depending on which platform software for install is being
updated for.
- Update the software selection modal to display software package
versions, including the package type (deb, rpm, or tar) for Linux
software packges.
- New activity feed item
- Update relevant tests


![ezgif-86da6f2b97d770](https://github.com/user-attachments/assets/6ae95bb7-f629-472e-b996-fcba1cf83e76)
_Note that the lower-right-hand image in this GIF is outdated and will
be updated with new content once this entire feature is integrated_


~- [ ] Changes file added for user-visible changes in `changes/`~ will
include in PR 2/2
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

- [x] Verified that any relevant UI is disabled when GitOps mode is
enabled

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
lucasmrod added a commit that referenced this pull request Sep 5, 2025
jacobshandling added a commit that referenced this pull request Sep 5, 2025
## PR 2/2 for #32037

- Implements update for the Linux setup experience from the end-user's
point of view (the "My device" page).
- Works in concert with the new endpoints implemented in
#32493
- My device page calls a new endpoint to get in-progress setup
experience software installations. If there are any, the page is
replaced with a "Setting up your device" page
- The UI polls this endpoint until all such installations are either
successful or failed (including canceled)
- Setting up your device page includes a table displaying the name and
status of each software installation
- Once all installations are finished (succeed/fail), renders the
regular My device page
- Add a handler for the new API call for relevant tests


![ezgif-6b54f32a7103ec](https://github.com/user-attachments/assets/cd94f92f-2daa-40a2-8fa1-643ed69a198c)

## Testing
Can use [this branch with fake
data](https://github.com/fleetdm/fleet/tree/32037-end-user-fake-data) to
help test this PR

- [x] Changes file added for user-visible changes in `changes/`
- [x] Added/updated automated tests - additional tests coming in
follow-up
- [x] QA'd all new/changed functionality manually

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants