fleet/server/datastore/mysql/android_hosts.go
2025-09-22 11:29:57 -04:00

113 lines
3.7 KiB
Go

package mysql
import (
"context"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/mdm/android"
"github.com/go-kit/log/level"
"github.com/jmoiron/sqlx"
)
func (ds *AndroidDatastore) CreateDeviceTx(ctx context.Context, tx sqlx.ExtContext, device *android.Device) (*android.Device, error) {
// Check for existing devices and duplicates
stmt := `SELECT id, device_id, enterprise_specific_id FROM android_devices WHERE device_id = ? OR enterprise_specific_id = ?`
var existing []android.Device
err := sqlx.SelectContext(ctx, tx, &existing, stmt, device.DeviceID, device.EnterpriseSpecificID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "checking for existing Android device")
}
switch {
case len(existing) == 0:
return ds.insertDevice(ctx, device, tx)
case len(existing) == 1:
device.ID = existing[0].ID
return ds.updateDevice(ctx, device, tx)
case len(existing) == 2:
err = ds.deleteDuplicate(ctx, device, tx, existing)
if err != nil {
return nil, err
}
return ds.updateDevice(ctx, device, tx)
default:
// Should never happen
return nil, ctxerr.New(ctx, "unexpected number of existing devices")
}
}
func (ds *AndroidDatastore) deleteDuplicate(ctx context.Context, device *android.Device, tx sqlx.ExtContext, existing []android.Device) error {
// Duplicates should never happen. We log error and try to handle it gracefully.
level.Error(ds.logger).Log("msg", "Found two Android devices with the same device ID or enterprise specific ID", "device_id",
device.DeviceID, "enterprise_specific_id", device.EnterpriseSpecificID)
// It should not matter which duplicate we delete since the other one will be overwritten.
err := ds.deleteDevice(ctx, tx, existing[0].ID)
if err != nil {
return ctxerr.Wrap(ctx, err, "deleting duplicate device")
}
device.ID = existing[1].ID
return nil
}
func (ds *AndroidDatastore) deleteDevice(ctx context.Context, tx sqlx.ExtContext, id uint) error {
deleteStmt := `DELETE FROM android_devices WHERE id = ?`
_, err := tx.ExecContext(ctx, deleteStmt, id)
return err
}
func (ds *AndroidDatastore) insertDevice(ctx context.Context, device *android.Device, tx sqlx.ExtContext) (*android.Device, error) {
stmt := `INSERT INTO
android_devices
(
host_id,
device_id,
enterprise_specific_id,
last_policy_sync_time,
applied_policy_id,
applied_policy_version
)
VALUES (?, ?, ?, ?, ?, ?)`
result, err := tx.ExecContext(ctx, stmt,
device.HostID,
device.DeviceID,
device.EnterpriseSpecificID,
device.LastPolicySyncTime,
device.AppliedPolicyID,
device.AppliedPolicyVersion,
)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "inserting device")
}
id, err := result.LastInsertId()
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting android_devices last insert ID")
}
device.ID = uint(id) // nolint:gosec
return device, nil
}
func (ds *AndroidDatastore) updateDevice(ctx context.Context, device *android.Device, tx sqlx.ExtContext) (*android.Device, error) {
stmt := `
UPDATE android_devices SET
host_id = :host_id,
device_id = :device_id,
enterprise_specific_id = :enterprise_specific_id,
last_policy_sync_time = :last_policy_sync_time,
applied_policy_id = :applied_policy_id,
applied_policy_version = :applied_policy_version
WHERE id = :id`
stmt, args, err := sqlx.Named(stmt, device)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "binding parameters for updating Android device")
}
_, err = tx.ExecContext(ctx, stmt, args...)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "updating Android device")
}
return device, nil
}
func (ds *AndroidDatastore) UpdateDeviceTx(ctx context.Context, tx sqlx.ExtContext, device *android.Device) error {
_, err := ds.updateDevice(ctx, device, tx)
return err
}