diff --git a/server/service/integration_vpp_install_test.go b/server/service/integration_vpp_install_test.go index 21b7e22641..fd767afb4c 100644 --- a/server/service/integration_vpp_install_test.go +++ b/server/service/integration_vpp_install_test.go @@ -685,6 +685,20 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() { commandResultsResp.Results[0].ResultsMetadata["vpp_verify_timeout_seconds"], ) + // Verify the device/self-service endpoint also returns VPP metadata (#43957) + var deviceCmdResultsResp getMDMCommandResultsResponse + res := s.DoRawNoAuth("GET", fmt.Sprintf("/api/latest/fleet/device/%s/software/commands/%s/results", "foobar", installCmdUUID), nil, http.StatusOK) + err := json.NewDecoder(res.Body).Decode(&deviceCmdResultsResp) + require.NoError(t, err) + require.Len(t, deviceCmdResultsResp.Results, 1) + require.Equal(t, false, deviceCmdResultsResp.Results[0].ResultsMetadata["software_installed"]) + require.InEpsilon( + t, + float64(int(fleet.DefaultVPPInstallVerifyTimeout.Seconds())), + deviceCmdResultsResp.Results[0].ResultsMetadata["vpp_verify_timeout_seconds"], + 0.01, + ) + // ======================================================== // Mark installs as failed when MDM turned off on host // ======================================================== diff --git a/server/service/mdm.go b/server/service/mdm.go index 9500393678..b154383e69 100644 --- a/server/service/mdm.go +++ b/server/service/mdm.go @@ -929,6 +929,25 @@ func (svc *Service) getDeviceSoftwareMDMCommandResults(ctx context.Context, comm res.Hostname = host.Hostname } + // Enrich VPP command results with install status and verify timeout metadata, + // matching what the admin path does in GetMDMCommandResults. + if len(results) > 0 { + installed, err := svc.ds.GetVPPAppInstallStatusByCommandUUID(ctx, commandUUID) + if err != nil { + svc.logger.DebugContext(ctx, "failed to check if VPP app is installed", "err", err, "command_uuid", commandUUID) + } else { + for _, res := range results { + if res.RequestType == "InstallApplication" { + if res.ResultsMetadata == nil { + res.ResultsMetadata = make(map[string]any) + } + res.ResultsMetadata["software_installed"] = installed + res.ResultsMetadata["vpp_verify_timeout_seconds"] = int(svc.config.Server.VPPVerifyTimeout.Seconds()) + } + } + } + } + return results, nil } diff --git a/server/service/mdm_test.go b/server/service/mdm_test.go index e3046f6785..9ae1322635 100644 --- a/server/service/mdm_test.go +++ b/server/service/mdm_test.go @@ -2859,3 +2859,73 @@ func TestNewMDMProfilePremiumOnlyAndroid(t *testing.T) { }) } } + +func TestGetDeviceSoftwareMDMCommandResultsVPPMetadata(t *testing.T) { + ds := new(mock.Store) + cfg := config.TestConfig() + cfg.Server.VPPVerifyTimeout = 30 * time.Second // non-default to distinguish from frontend fallback of 600s + svc, ctx := newTestServiceWithConfig(t, ds, cfg, nil, nil, &TestServerOpts{SkipCreateTestUsers: true}) + + testHost := &fleet.Host{ID: 1, UUID: "host-uuid-1", Hostname: "test-host"} + const testCommandUUID = "cmd-uuid-1" + + t.Run("populates metadata with timeout and install status", func(t *testing.T) { + ds.GetVPPCommandResultsFunc = func(ctx context.Context, commandUUID string, hostUUID string) ([]*fleet.MDMCommandResult, error) { + return []*fleet.MDMCommandResult{ + {HostUUID: hostUUID, CommandUUID: commandUUID, RequestType: "InstallApplication"}, + }, nil + } + ds.GetVPPAppInstallStatusByCommandUUIDFunc = func(ctx context.Context, commandUUID string) (bool, error) { + return true, nil + } + + deviceCtx := test.HostContext(ctx, testHost) + results, err := svc.GetMDMCommandResults(deviceCtx, testCommandUUID, "") + require.NoError(t, err) + require.Len(t, results, 1) + require.NotNil(t, results[0].ResultsMetadata) + require.Equal(t, true, results[0].ResultsMetadata["software_installed"]) + require.Equal(t, 30, results[0].ResultsMetadata["vpp_verify_timeout_seconds"]) + require.Equal(t, testHost.Hostname, results[0].Hostname) + require.True(t, ds.GetVPPCommandResultsFuncInvoked) + require.True(t, ds.GetVPPAppInstallStatusByCommandUUIDFuncInvoked) + }) + + t.Run("returns results without metadata on install status error", func(t *testing.T) { + ds.GetVPPCommandResultsFuncInvoked = false + ds.GetVPPAppInstallStatusByCommandUUIDFuncInvoked = false + + ds.GetVPPCommandResultsFunc = func(ctx context.Context, commandUUID string, hostUUID string) ([]*fleet.MDMCommandResult, error) { + return []*fleet.MDMCommandResult{ + {HostUUID: hostUUID, CommandUUID: commandUUID, RequestType: "InstallApplication"}, + }, nil + } + ds.GetVPPAppInstallStatusByCommandUUIDFunc = func(ctx context.Context, commandUUID string) (bool, error) { + return false, errors.New("db error") + } + + deviceCtx := test.HostContext(ctx, testHost) + results, err := svc.GetMDMCommandResults(deviceCtx, testCommandUUID, "") + require.NoError(t, err) + require.Len(t, results, 1) + require.Nil(t, results[0].ResultsMetadata) + require.True(t, ds.GetVPPCommandResultsFuncInvoked) + require.True(t, ds.GetVPPAppInstallStatusByCommandUUIDFuncInvoked) + }) + + t.Run("skips install status check on empty results", func(t *testing.T) { + ds.GetVPPCommandResultsFuncInvoked = false + ds.GetVPPAppInstallStatusByCommandUUIDFuncInvoked = false + + ds.GetVPPCommandResultsFunc = func(ctx context.Context, commandUUID string, hostUUID string) ([]*fleet.MDMCommandResult, error) { + return []*fleet.MDMCommandResult{}, nil + } + + deviceCtx := test.HostContext(ctx, testHost) + results, err := svc.GetMDMCommandResults(deviceCtx, testCommandUUID, "") + require.NoError(t, err) + require.Empty(t, results) + require.True(t, ds.GetVPPCommandResultsFuncInvoked) + require.False(t, ds.GetVPPAppInstallStatusByCommandUUIDFuncInvoked) + }) +}