ddmsync

package
v0.9.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package ddmsync enqueues declarative synchronization commands and clears declarative state on enrollment lifecycle events.

Design

Notifier drains persistent changes by enrollment, coalesces recent edits, builds DeclarativeManagement commands with tokens and calls the normal service enqueue path. Failed work retains attempts and retry state; pushes and events are separate from device acknowledgement. Kick reduces polling latency without making the engine depend on dispatch.

ServiceHook uses enrollment storage to find dependent user channels and calls Engine.ClearEnrollment on Authenticate and CheckOut. Cleanup across MDM and DDM stores is not a distributed transaction.

References

Index

Constants

View Source
const (
	DefaultNotifyWindow = 2 * time.Second
	DefaultNotifyPoll   = time.Second
	DefaultNotifyBatch  = 500
)

Defaults for NotifierConfig.

View Source
const DefaultDedupeKey = "ddm"

DefaultDedupeKey is the dedupe key NotifierConfig.DedupeKey uses when it is not set. It suppresses a second DeclarativeManagement while one is still pending for the same enrollment.

Suppression is safe for this command specifically, and only for it: the command is a doorbell, not a payload. A device that receives it fetches the current tokens and declaration items, so a pending command already carries every change made since it was queued. It would not be safe for a command whose payload is the instruction, which is why the key is opt-in per caller rather than a property of the queue.

Variables

View Source
var ErrNotifierConfig = errors.New("ddmsync: notifier needs Store, Tokens, and Enqueuer")

ErrNotifierConfig reports a missing required dependency.

Functions

This section is empty.

Types

type DrainResult

type DrainResult struct {
	// Deferred enrollments were left for a later drain because a change
	// arrived within Window.
	Deferred int
	// Queued enrollments received a new command; Deduped ones already had
	// one pending and were pushed anyway; Skipped ones cannot be
	// commanded (disabled or unknown) and were completed without a push.
	Queued, Deduped, Skipped int
	// Failed enrollments had their change rows scheduled for a retry.
	Failed int
	// Pushed counts enrollments handed to the Pusher.
	Pushed int
}

DrainResult counts what one drain did.// DrainResult counts what one drain did.

func (DrainResult) Empty

func (r DrainResult) Empty() bool

Empty reports a drain that did nothing, so a caller can skip reporting it.

type Enqueuer

type Enqueuer interface {
	Enqueue(
		ctx context.Context,
		ids []mdm.EnrollmentID,
		cmd *mdm.Command,
		o storage.EnqueueOptions,
	) (storage.EnqueueResult, error)
}

Enqueuer queues a command for enrollments; *service.Core satisfies it.

type Notifier

type Notifier struct {
	// contains filtered or unexported fields
}

Notifier turns committed change rows into DeclarativeManagement commands and pushes (decision record 0022).

func NewNotifier

func NewNotifier(cfg NotifierConfig) (*Notifier, error)

NewNotifier validates the configuration and applies defaults.

func (*Notifier) DrainOnce

func (n *Notifier) DrainOnce(ctx context.Context) (DrainResult, error)

DrainOnce processes the due change rows once. Store failures are returned; per-enrollment failures are recorded on the rows with backoff.

func (*Notifier) Kick

func (n *Notifier) Kick()

Kick requests an immediate drain instead of waiting for the next poll. It is nonblocking and can be called after an administrative write.

func (*Notifier) Run

func (n *Notifier) Run(ctx context.Context) error

Run drains until ctx is cancelled, every Poll or on Kick. Drain errors are logged and retried at the next tick.

type NotifierConfig

type NotifierConfig struct {
	Store    ddm.ChangeStore
	Tokens   TokenSource
	Enqueuer Enqueuer
	Pusher   Pusher
	Bus      event.Publisher
	Clock    clock.Clock
	Logger   *slog.Logger
	// Window defers an enrollment while its newest change is younger than
	// this, so a burst of uploads becomes one command. Default 2s.
	Window time.Duration
	// Poll is how often Run drains without a Kick. Default 1s.
	Poll time.Duration
	// Batch bounds the change rows read per drain. Default 500.
	Batch int
	// Backoff maps the attempt count to the retry delay. Default
	// storage.NotNowBackoff.
	Backoff func(attempt int) time.Duration
	// DedupeKey suppresses a new DeclarativeManagement while one is still
	// pending for the same enrollment. Nil uses DefaultDedupeKey; an empty
	// string turns suppression off, so every drain queues a command.
	//
	// It is a pointer for the same reason service.Config.ValidateTargets is:
	// the zero value has to mean "not set" so that "" can mean "off".
	// Whether to suppress is the consumer's decision, not this package's.
	DedupeKey *string
	// OnDrain, when set, is called with the outcome of every drain. A
	// suppressed command is counted in DrainResult.Deduped and is otherwise
	// invisible, which is the whole problem with suppression: it has to be
	// observable to be defensible.
	OnDrain func(ctx context.Context, res DrainResult)
}

NotifierConfig configures NewNotifier. Store, Tokens, and Enqueuer are required; Pusher, Bus, and Logger are optional.

type Pusher

type Pusher interface {
	Notify(ctx context.Context, ids []mdm.EnrollmentID) (map[mdm.EnrollmentID]push.Result, error)
}

Pusher sends APNs wake-ups; *pushnotify.Notifier satisfies it.

type ServiceHook

type ServiceHook struct {
	// contains filtered or unexported fields
}

ServiceHook clears DDM state after successful checkout or authentication, including the device's user channels. Cleanup failures are logged.

func NewServiceHook

func NewServiceHook(e *ddm.Engine, enrollments storage.EnrollmentStore, log *slog.Logger) *ServiceHook

NewServiceHook builds the hook; enrollments is used to find a device's user channels.

func (*ServiceHook) After

func (h *ServiceHook) After(ctx context.Context, c *dmhook.Call, err error)

After implements dmhook.Hook.

func (*ServiceHook) Before

func (h *ServiceHook) Before(ctx context.Context, c *dmhook.Call) (context.Context, error)

type TokenSource

type TokenSource interface {
	Tokens(ctx context.Context, id mdm.EnrollmentID) ([]byte, error)
}

TokenSource renders an enrollment's TokensResponse; *ddm.Engine satisfies it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL