reconcile

package
v0.4.15 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package reconcile computes the 3-way plan (git desired ↔ committed baseline ↔ live ABM) for bidirectional, newest-wins sync. Phase 1 computes + reports the plan only; Phase 2 executes it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action

type Action string

Action is the reconcile verb chosen for one config in the plan.

const (
	Create    Action = "create-abm"   // new in git → POST
	Update    Action = "update-abm"   // changed in git → PATCH
	Pull      Action = "pull-git"     // changed in ABM → write into git
	PullNew   Action = "pull-new-git" // new in ABM (console-created) → write into git
	DeleteABM Action = "delete-abm"   // removed from git → DELETE (prune, gated)
	DeleteGit Action = "delete-git"   // removed from ABM → remove git file
	Conflict  Action = "conflict"     // changed in BOTH → newest-wins (resolved at apply)
)

Reconcile actions, one per planned change to a config.

type Applier

type Applier interface {
	CreateConfiguration(name, xml string, platforms []string) (id, updated string, err error)
	UpdateConfiguration(id, name, xml string) (updated string, err error)
	DeleteConfiguration(id string) error
	CreateBlueprint(name, description string, members map[string][]string) (*ab.Resource, error)
	AddBlueprintMembers(bpID, rel, memberType string, ids []string) error
	RemoveBlueprintMembers(bpID, rel, memberType string, ids []string) error
}

Applier is the subset of the Apple Business write API the executor needs. It is an interface so Apply can be unit-tested with a fake — no production writes.

type Archiver

type Archiver interface {
	Archive(name, reason string, xml []byte, meta map[string]string) (path string, err error)
}

Archiver files a pre-overwrite live profile + sidecar (see internal/archive). Injected so Apply stays disk-free and testable; it returns the archive path.

type BlueprintAction

type BlueprintAction string

BlueprintAction is the reconcile verb for a blueprint or one of its members.

const (
	Attach        BlueprintAction = "attach-config" // member in git, not in ABM → POST membership (per collection below)
	Detach        BlueprintAction = "detach-config" // member in ABM, not in git → DELETE membership (gated --prune)
	AttachApp     BlueprintAction = "attach-app"
	DetachApp     BlueprintAction = "detach-app"
	AttachPackage BlueprintAction = "attach-package"
	DetachPackage BlueprintAction = "detach-package"
	AttachDevice  BlueprintAction = "attach-device"
	DetachDevice  BlueprintAction = "detach-device"
	AttachUser    BlueprintAction = "attach-user"
	DetachUser    BlueprintAction = "detach-user"
	AttachGroup   BlueprintAction = "attach-group"
	DetachGroup   BlueprintAction = "detach-group"
	BlueprintNew  BlueprintAction = "blueprint-new"   // blueprint in git, not in ABM → POST blueprints, then attach members
	BlueprintGone BlueprintAction = "blueprint-adopt" // blueprint in ABM, not in git → run seed to adopt (reported)
)

Blueprint reconcile actions. Attach/detach verbs are collection-qualified; the configurations pair keeps its original values ("attach-config" / "detach-config") so existing JSON consumers are unaffected.

func (BlueprintAction) IsAttach added in v0.4.15

func (a BlueprintAction) IsAttach() bool

IsAttach / IsDetach classify a membership verb regardless of collection.

func (BlueprintAction) IsDetach added in v0.4.15

func (a BlueprintAction) IsDetach() bool

IsDetach reports whether the action is a membership detach (any collection).

type BlueprintItem

type BlueprintItem struct {
	Blueprint  string          `json:"blueprint"`
	BPID       string          `json:"bp_id,omitempty"` // empty for a git-only blueprint (filled by the create at apply time)
	Action     BlueprintAction `json:"action"`
	Collection string          `json:"collection,omitempty"` // member collection key (ab.Collection*); empty on blueprint-level rows and legacy items (= configurations)
	// Config / ConfigID carry the MEMBER display name and ABM id for every
	// collection — the JSON keys predate non-config membership and are kept
	// stable for existing consumers (abgui decodes them).
	Config      string `json:"config,omitempty"`
	ConfigID    string `json:"config_id,omitempty"`
	Description string `json:"description,omitempty"` // blueprint-new: the manifest description the create sends
	Detail      string `json:"detail"`
}

BlueprintItem is one planned blueprint change.

func (BlueprintItem) IsActionable added in v0.4.3

func (it BlueprintItem) IsActionable() bool

IsActionable reports whether sync can perform this item in the current run. blueprint-new is a real CREATE (API v2.0). An attach needs a resolved member id — an empty id means the row is blocked until the member exists in ABM (a config not yet created/adopted, or a member name the tenant doesn't know).

type BlueprintOutcome

type BlueprintOutcome struct {
	Blueprint string          `json:"blueprint"`
	Config    string          `json:"config,omitempty"`
	Action    BlueprintAction `json:"action"`
	Status    string          `json:"status"` // "done" | "skipped" | "error"
	Detail    string          `json:"detail"`
}

BlueprintOutcome records what happened to one planned blueprint item.

type BlueprintPlan

type BlueprintPlan struct {
	Items []BlueprintItem `json:"items"`
}

BlueprintPlan is the ordered set of planned blueprint changes.

func ComputeBlueprints

func ComputeBlueprints(desired map[string]gitops.BlueprintSpec, live []ab.LiveBlueprint, idByName map[string]map[string]string) *BlueprintPlan

ComputeBlueprints diffs the git blueprint manifests against live ABM blueprints, per managed member collection. idByName maps collection key → member display name → ABM id; for configurations it is built from the sync baseline (or the post-apply baseline, so freshly-created configs resolve) — an ownership gate, not a full-tenant list. A blueprint is matched by name across git and ABM; a git-only blueprint plans a CREATE followed by its member attaches, and an ABM-only blueprint is reported for adoption (never deleted).

func (*BlueprintPlan) HasChanges

func (p *BlueprintPlan) HasChanges() bool

HasChanges reports whether the plan contains any items (actionable or reported).

func (*BlueprintPlan) HasReconcilableChanges

func (p *BlueprintPlan) HasReconcilableChanges() bool

HasReconcilableChanges reports whether the plan has drift that sync can act on. Reported-only rows and attach rows without a member id are excluded, so --exit-on-diff does not loop forever and --apply does not confirm then skip.

func (*BlueprintPlan) ReconcilableCount

func (p *BlueprintPlan) ReconcilableCount() int

ReconcilableCount is the number of items apply can perform (for a confirm prompt).

type BlueprintResult

type BlueprintResult struct {
	Outcomes []BlueprintOutcome `json:"outcomes"`
	Writes   int                `json:"writes"`
	Errors   int                `json:"errors"`
	Skipped  int                `json:"skipped"`
}

BlueprintResult summarizes a blueprint apply run.

type Engine

type Engine struct {
	Client   Applier
	Archiver Archiver
	Files    FileStore
}

Engine executes a reconcile plan against a live tenant. It archives before every overwrite or delete and keeps the committed baseline exact so the next 3-way diff is correct.

func (*Engine) Apply

func (e *Engine) Apply(p *Plan, desired map[string][]byte, live []ab.LiveConfig, base *state.State, opts Opts) *Result

Apply executes the plan. Every error is captured per-item in the Result (Errors count) rather than aborting the run, so independent configs still converge; the baseline (base) is mutated in place and should be saved by the caller only after Apply returns. Archiving always precedes the write it protects — if the archive fails, the write is skipped so the audit trail is never bypassed.

func (*Engine) ApplyBlueprints

func (e *Engine) ApplyBlueprints(p *BlueprintPlan, opts Opts, priorWrites int) *BlueprintResult

ApplyBlueprints executes the blueprint plan: create git-only blueprints, then attach (always) and detach (only with --prune) members via per-member POST/DELETE (the relationship is additive/merges, so this converges). An attach whose blueprint was created this run resolves its id from that create; if the create failed or was skipped, the attach skips benignly. priorWrites is the tenant writes already spent this run (e.g. by config apply) so --limit-writes is a single shared budget. Reported-only items (adopt) are surfaced as skips.

type FileStore

type FileStore interface {
	WriteConfig(name string, content []byte) error
	RemoveConfig(name string) error
}

FileStore is the git-side profile tree (see internal/gitops). Pull writes a file; an ABM-side delete removes one.

type Item

type Item struct {
	Name   string `json:"name"`
	Action Action `json:"action"`
	Detail string `json:"detail"`
}

Item is one planned change: the config name, the action, and a human-readable detail.

type Opts

type Opts struct {
	Prune       bool     // enable DeleteABM (off by default — never prune unasked)
	LimitWrites int      // circuit breaker: max tenant writes per run (0 = unlimited)
	Platforms   []string // configuredForPlatforms for newly-created configs
	Progress    func(string)
	// GitTime resolves the git-side timestamp of a config (last commit time, else
	// file mtime) for newest-wins conflict resolution. ok=false → the timestamp is
	// unknown, so the conflict is skipped rather than guessed (never clobber a side
	// on missing information).
	GitTime func(name string) (t time.Time, ok bool)
}

Opts tunes one apply run.

type Outcome

type Outcome struct {
	Name    string `json:"name"`
	Action  Action `json:"action"` // the effective action (a Conflict resolves to Update or Pull)
	Status  string `json:"status"` // "done" | "skipped" | "error"
	Detail  string `json:"detail"`
	Archive string `json:"archive,omitempty"` // archive path, when one was written
}

Outcome records what happened to one planned item.

type Plan

type Plan struct {
	Items []Item `json:"items"`
}

Plan is the ordered set of changes the reconcile produced.

func Compute

func Compute(desired map[string][]byte, base *state.State, live []ab.LiveConfig) *Plan

Compute diffs desired (git lib/) vs baseline (state) vs live (ABM). Only changed items are returned; unchanged configs are omitted.

func ComputeGitSourceOfTruth added in v0.4.5

func ComputeGitSourceOfTruth(desired map[string][]byte, live []ab.LiveConfig) *Plan

ComputeGitSourceOfTruth treats git as the complete desired state. Live Apple Business configs are only inspected to decide which POST/PATCH/DELETE writes are needed; live-only configs are not pulled into git.

func (*Plan) HasChanges

func (p *Plan) HasChanges() bool

HasChanges reports whether the plan contains any changes.

type Result

type Result struct {
	Outcomes []Outcome `json:"outcomes"`
	Writes   int       `json:"writes"`  // tenant writes performed (POST/PATCH/DELETE)
	Errors   int       `json:"errors"`  // items that failed
	Skipped  int       `json:"skipped"` // items intentionally not applied (prune off / limit reached / unresolved)
}

Result summarizes an apply run.

Jump to

Keyboard shortcuts

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