edgesync

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package edgesync is the generic poll/report rails behind every edge sync channel — edge packs, local controls, and whatever comes after. Before this package, config_poll.go's heartbeat loop was the only precedent, and it is entirely Guard-specific (one hardcoded path, one fixed response struct, fields owned directly by Guard) — not something a second, unrelated sync channel could reuse without copying its shape wholesale. Syncer factors that shape out: a signed poll on an interval, change detection by content hash, a callback fired only on real change, and an optional signed report call — so a second sync channel is a few lines of wiring, not a copy of the first.

Syncer does not know or care what it is syncing. It moves bytes; the caller (edge-pack sync, local-controls sync) owns decoding, converging local state, and deciding what to report.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option func(*Syncer)

Option configures a Syncer at construction.

func WithHeaders

func WithHeaders(fn func() map[string]string) Option

WithHeaders attaches extra headers (e.g. hostname, a locally-tracked hash) to every poll GET, mirroring config_poll.go's heartbeatHeaders.

func WithOnUpdate

func WithOnUpdate(fn func(raw []byte)) Option

WithOnUpdate sets the callback fired with the raw response body whenever a poll's content differs from the last one seen. Optional — a Syncer with no OnUpdate is a poll-and-discard heartbeat (rarely useful) or, combined with WithReportBuilder alone, a report-only channel.

func WithReportBuilder

func WithReportBuilder(fn func() ([]byte, error)) Option

WithReportBuilder causes the poll loop to also report on every tick, regardless of whether the poll response changed: after each successful poll, onReport builds the current local state and Syncer POSTs it to the report path. Errors from onReport or the POST are swallowed — same best-effort posture as the poll itself. Requires WithReportPath.

func WithReportPath

func WithReportPath(path string) Option

WithReportPath sets the signed POST endpoint Report (and, if WithReportBuilder is also set, the per-tick auto-report) targets. A Syncer with no report path is poll-only — Report returns an error, and a report builder is never invoked.

type Syncer

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

Syncer polls a signed endpoint on an interval and invokes OnUpdate only when the raw response content actually changes (sha256 comparison) — a healthy steady-state poll that returns unchanged bytes is silent. It can also report local state back over a second signed endpoint, either per-tick (WithReportBuilder, mirrors today's "the pull doubles as the report" cadence) or on demand (Report, for reporting immediately after a local convergence rather than waiting for the next tick).

Poll errors are non-fatal — this is config distribution, not enforcement; a failed tick just retries next interval, the same philosophy as the existing heartbeat poller.

func New

func New(transport Transport, pollPath string, interval time.Duration, opts ...Option) *Syncer

New builds a Syncer. pollPath is the signed GET endpoint to watch for change; interval is the poll cadence. This package imposes no minimum interval — callers own that policy (flyedged's FLYEDGED_PACK_INTERVAL floor, for example).

func (*Syncer) Poll

func (s *Syncer) Poll(ctx context.Context) (raw []byte, changed bool, err error)

Poll does one signed GET and reports whether the content changed since the last call this Syncer made (sha256 of the raw bytes). Exported so a caller wanting manual control — a one-shot check rather than a background loop — never needs Start/Stop at all.

When the Transport supports conditional GETs, the last-seen hash goes out as If-None-Match and an unchanged response comes back as a bodiless 304 — so a steady-state poll transfers nothing. A 304 returns (nil, false, nil): no bytes and no change, which is exactly what the caller already handles, since it only acts on changed == true.

func (*Syncer) Report

func (s *Syncer) Report(ctx context.Context, body []byte) error

Report signs and POSTs body to the configured report path. Returns an error if no report path was set via WithReportPath — reporting is opt-in per channel.

func (*Syncer) Start

func (s *Syncer) Start()

Start launches the poll loop in its own goroutine (idempotent — a second Start on an already- running Syncer is a no-op). Polls immediately so state is fresh without waiting a full interval, then on the configured tick.

func (*Syncer) Stop

func (s *Syncer) Stop()

Stop signals the poll loop to exit and blocks until it has. Safe to call on a Syncer that was never Started.

type Transport

type Transport interface {
	GetSigned(ctx context.Context, path string, headers map[string]string) ([]byte, error)
	PostSigned(ctx context.Context, path string, body []byte) ([]byte, error)
}

Transport is the signed HTTP capability a Syncer needs. *enforce.HTTPEnforcer already implements this (GetSigned/PostSigned are exported precisely because config_poll.go's signedGetter/connect.go's signedPoster seams needed them) — no new signing code, no new dependency. A test stub can substitute a fake satisfying the same two methods.

Jump to

Keyboard shortcuts

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