Files
fleet/server/mdm/android/service/reconcile_devices.go
T
Magnus JensenandJordan Montgomery 237105728e Reduce API Android usage (#36209)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #36138 

# 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.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

---------

Co-authored-by: Jordan Montgomery <elijah.jordan.montgomery@gmail.com>
2025-11-25 09:39:06 -03:00

108 lines
3.7 KiB
Go

package service
import (
"context"
"fmt"
"time"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
)
// ReconcileAndroidDevices polls AMAPI for devices that Fleet still considers enrolled
// and flips them to unenrolled if Google reports them missing (404).
// This complements (does not replace) Pub/Sub DELETED handling.
func ReconcileAndroidDevices(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, licenseKey string) error {
appConfig, err := ds.AppConfig(ctx)
if err != nil {
return ctxerr.Wrap(ctx, err, "get app config")
}
if !appConfig.MDM.AndroidEnabledAndConfigured {
return nil
}
// Ensure an enterprise exists, otherwise nothing to do, and keep its ID to build device resource names.
enterprise, err := ds.GetEnterprise(ctx)
if err != nil {
return ctxerr.Wrap(ctx, err, "get android enterprise")
}
client := newAMAPIClient(ctx, logger, licenseKey)
// Best-effort set authentication secret for proxy client usage (no-op for Google client).
if assets, err := ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetAndroidFleetServerSecret}, nil); err == nil {
if asset, ok := assets[fleet.MDMAssetAndroidFleetServerSecret]; ok && len(asset.Value) > 0 {
_ = client.SetAuthenticationSecret(string(asset.Value))
}
}
devices, err := ds.ListAndroidEnrolledDevicesForReconcile(ctx)
if err != nil {
return ctxerr.Wrap(ctx, err, "list enrolled android devices for reconcile")
}
if len(devices) == 0 {
return nil
}
// Make a list of all devices in Google
deviceNameMap := make(map[string]struct{})
pageToken := ""
for {
// We use the partial call here, to avoid getting all data for a device when we only need a subset (name).
// should help with request speeds, and also cost for website in terms of network egress.
resp, err := client.EnterprisesDevicesListPartial(ctx, enterprise.Name(), pageToken)
if err != nil {
return ctxerr.Wrap(ctx, err, "listing android devices from AMAPI")
}
for _, dev := range resp.Devices {
deviceNameMap[dev.Name] = struct{}{}
}
if resp.NextPageToken == "" {
break
}
pageToken = resp.NextPageToken
}
checked := 0
unenrolled := 0
for _, dev := range devices {
if dev == nil || dev.DeviceID == "" {
continue
}
checked++
deviceName := fmt.Sprintf("%s/devices/%s", enterprise.Name(), dev.DeviceID)
_, ok := deviceNameMap[deviceName]
switch {
case ok:
// Device exists, no-op.
continue
case !ok:
if _, derr := ds.SetAndroidHostUnenrolled(ctx, dev.HostID); derr != nil {
level.Error(logger).Log("msg", "failed to mark android host unenrolled during reconcile", "host_id", dev.HostID, "err", derr)
continue
}
// Emit system activity to mirror Pub/Sub DELETED handling.
var displayName, serial string
if hosts, herr := ds.ListHostsLiteByIDs(ctx, []uint{dev.HostID}); herr == nil && len(hosts) == 1 && hosts[0] != nil {
displayName = hosts[0].DisplayName()
serial = hosts[0].HardwareSerial
}
if aerr := ds.NewActivity(ctx, nil, fleet.ActivityTypeMDMUnenrolled{
HostSerial: serial,
HostDisplayName: displayName,
InstalledFromDEP: false,
Platform: "android",
}, nil, time.Now()); aerr != nil {
level.Debug(logger).Log("msg", "failed to create mdm_unenrolled activity during android reconcile", "host_id", dev.HostID, "err", aerr)
}
unenrolled++
level.Debug(logger).Log("msg", "android device missing in Google; marked unenrolled", "host_id", dev.HostID, "device", deviceName)
}
}
level.Debug(logger).Log("msg", "android reconcile complete", "checked", checked, "unenrolled", unenrolled)
return nil
}