Documentation
¶
Overview ¶
internal/fleet/changelog.go
Package fleet provides the runtime-image discovery primitives for go-code.
It defines the core data carriers (RuntimeImage, Target, Filter), the Probe interface, a Registry for mapping URL schemes to Probe drivers, and a pure Diff function for comparing pinned source images against discovered runtime images.
Package fleet imports internal/polyglot/pinned for the PinnedImage type. All driver packages (fleet/docker, fleet/ssh, …) depend on fleet, not the other way around.
Index ¶
- Variables
- func IsValidFilter(s string) bool
- func MatchesFilter(s string, r RuntimeImage) bool
- func ParseImageRef(ref string) (image, tag, digest, invalidDigestReason string)
- type Changelog
- type ChangelogCommit
- type DiffStatus
- type Filter
- type ImageDiff
- type Probe
- type Registry
- type RuntimeImage
- type SiblingDriftRow
- type SiblingVariant
- type Target
- type TargetReportLike
Constants ¶
This section is empty.
Variables ¶
var DiffStatusPriority = map[DiffStatus]int{ DiffTagDrift: 0, DiffDigestDrift: 1, DiffUnresolved: 2, DiffOnlySource: 3, DiffOnlyRuntime: 4, DiffMatch: 5, }
DiffStatusPriority maps DiffStatus to a sort-order integer used by Diff() for stable per-target output. Lower number = higher priority (appears first).
External code wanting a DIFFERENT priority order (e.g. LLM-summary presentation) MUST declare its own table rather than mutating this one; see SummaryStatusPriority.
var ErrInvalidTarget = errors.New("fleet: invalid target")
ErrInvalidTarget is returned by ParseTarget when the input cannot be parsed into a valid Target.
var ErrSchemeUnknown = errors.New("fleet: no probe registered for scheme")
ErrSchemeUnknown is returned by Registry.Get when no probe is registered.
var SummaryStatusPriority = map[DiffStatus]int{ DiffTagDrift: 0, DiffDigestDrift: 1, DiffUnresolved: 2, DiffOnlyRuntime: 3, DiffOnlySource: 4, DiffMatch: 5, }
SummaryStatusPriority is the order used to rank entries when summarising fleet drift for an LLM prompt (cmd/go-code Phase 7). DELIBERATELY differs from DiffStatusPriority: OnlyRuntime ranks above OnlySource here because a running unpinned container is more actionable in incident triage than a pinned image with no running instance.
Lower number = higher priority (appears first). The zero value for unknown statuses sorts last — callers should provide explicit fallback (e.g. 99) for statuses not in this map.
Functions ¶
func IsValidFilter ¶
IsValidFilter reports whether s is acceptable as a filter value passed to Filter.Service. Allowed chars: [a-zA-Z0-9._-]. Empty string is allowed (means "no filter").
This is the canonical implementation; internal/fleet/docker and internal/fleet/ssh both delegate to this. The character set is intentionally ASCII-only — Docker container and Compose service names are always ASCII, and a tighter allow-list reduces injection surface.
func MatchesFilter ¶
func MatchesFilter(s string, r RuntimeImage) bool
MatchesFilter reports whether a runtime image matches the requested service filter, using the documented priority order:
- Exact Container name match
- com.docker.compose.service label value (carried in RuntimeImage.Service)
Empty filter (s == "") always matches.
Both comparisons are case-sensitive.
func ParseImageRef ¶
ParseImageRef splits a Docker image reference into (image, tag, digest, invalidDigestReason).
Rules:
- If ref contains '@': right side is the digest candidate. If it does NOT begin with "sha256:", digest is set to "" and invalidDigestReason describes why. Otherwise digest = the full "sha256:..." string.
- On the remaining ref (or whole if no '@'): find the LAST ':'. If the substring after it does NOT contain '/', that is the tag. Otherwise the whole thing is the image (the colon is part of a registry host:port like "localhost:5000/foo").
- If both tag and digest are empty after the above: tag = "latest".
invalidDigestReason is "" when digest is valid or absent.
Driver call sites (fleet/docker, fleet/ssh) ignore invalidDigestReason, silently dropping invalid digests — matching the prior silent-drop behaviour. The polyglot/pinned parser surfaces it via PinnedImage.Unresolved instead.
Types ¶
type Changelog ¶
type Changelog struct {
Repo string `json:"repo"` // "XTLS/Xray-core"
Base string `json:"base"` // "26.4.25" (actually-resolved tag)
Head string `json:"head"` // "26.5.3"
Status string `json:"status"` // "ahead" / "behind" / "diverged" / "identical" / "unresolved"
Commits []ChangelogCommit `json:"commits,omitempty"` // capped at 20, head→base order
Truncated bool `json:"truncated,omitempty"` // true if upstream had >20 commits
Resolved bool `json:"resolved"` // false if mapping or compare failed
Reason string `json:"reason,omitempty"` // when Resolved=false
URL string `json:"url,omitempty"` // GitHub web URL for the compare view
}
Changelog holds the result of correlating two upstream image tags against a public GitHub repository via the Compare API.
Populated by internal/fleet/upstream.Enrich for DiffTagDrift rows. Attached to ImageDiff.Changelog (nil for non-TagDrift rows).
type ChangelogCommit ¶
type ChangelogCommit struct {
SHA string `json:"sha"`
Author string `json:"author"`
Date string `json:"date"` // ISO-8601, from author date
Subject string `json:"subject"` // first line of commit message
}
ChangelogCommit is one commit entry in a Changelog.
type DiffStatus ¶
type DiffStatus string
DiffStatus enumerates the possible diff outcomes for one image.
const ( DiffMatch DiffStatus = "Match" DiffTagDrift DiffStatus = "TagDrift" DiffDigestDrift DiffStatus = "DigestDrift" DiffOnlySource DiffStatus = "OnlySource" // pinned in repo, not running anywhere DiffOnlyRuntime DiffStatus = "OnlyRuntime" // running but not pinned in repo DiffUnresolved DiffStatus = "Unresolved" // pinned image had Unresolved set; cannot compare )
type Filter ¶
type Filter struct {
Service string // matches container name OR com.docker.compose.service label OR plain compose service
Labels map[string]string // exact label-value match; reserved for v2 — drivers may ignore for MVP
}
Filter narrows what a probe returns. Empty fields mean no constraint.
type ImageDiff ¶
type ImageDiff struct {
Image string // the registry+repo identifier this row is about
Pinned *pinned.PinnedImage
Runtime *RuntimeImage
Status DiffStatus
Explanation string // one-line human-readable summary
Changelog *Changelog `json:"changelog,omitempty"` // populated for DiffTagDrift rows when upstream enrichment succeeds
}
ImageDiff is one row of the per-target report. Either Pinned or Runtime (or both) is non-nil — never both nil.
func Diff ¶
func Diff(pinnedImgs []pinned.PinnedImage, runtimeImgs []RuntimeImage) []ImageDiff
Diff computes per-image diffs between pinned (source) and runtime (probe) sets.
Matching: by Image string (registry+repo, no tag, no digest). Case-sensitive.
Semantics per image group:
- Pinned only, no Runtime → DiffOnlySource (Pinned set, Runtime nil)
- Runtime only, no Pinned → DiffOnlyRuntime (Pinned nil, Runtime set)
- Pinned.Unresolved != "" → DiffUnresolved (Pinned set, Runtime set if available)
- Both present, same Tag, same Digest (or both Digest=="") → DiffMatch
- Both present, same Tag, different Digest (both non-empty) → DiffDigestDrift
- Both present, different Tag → DiffTagDrift
One-side-has-digest, other-does-not: treated as DiffMatch (no evidence of drift; runtime probes may not surface digests).
When the same Image appears multiple times in either input (e.g. two compose services referencing nginx), items are sorted within each Image group before pairing:
- Pinned: by Service, then Source, then Line
- Runtime: by Container, then StartedAt
Surplus on either side gets OnlySource / OnlyRuntime rows.
Output is sorted: by Status priority (TagDrift > DigestDrift > Unresolved > OnlySource > OnlyRuntime > Match), then by Image asc, then by Pinned.Service / Runtime.Container — deterministic.
type Probe ¶
type Probe interface {
Scheme() string
List(ctx context.Context, t Target, f Filter) ([]RuntimeImage, error)
}
Probe is the runtime-image discovery primitive. One implementation per Scheme.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps a target scheme to its Probe driver.
func (*Registry) Get ¶
Get returns the Probe for t.Scheme. Error if no probe is registered. Error wraps a sentinel ErrSchemeUnknown that callers can errors.Is against.
type RuntimeImage ¶
type RuntimeImage struct {
Container string // container name (preferred) or short id
Image string // registry+repo, no tag, no digest
Tag string // resolved tag; "" if image lacks one
Digest string // sha256:...; "" if not surfaced by probe
State string // "running" / "exited" / "restarting" / etc — probe-driver canonical
StartedAt time.Time // zero if unknown
Service string // com.docker.compose.service label value if present, else ""
}
RuntimeImage is one container image discovered by a Probe on a target host.
type SiblingDriftRow ¶
type SiblingDriftRow struct {
Image string `json:"image"` // registry+repo, no tag/digest
Variants []SiblingVariant `json:"variants"`
}
SiblingDriftRow groups same-Image runtime entries across targets whose tag or digest differs. Populated by SiblingDiff over a slice of TargetReports (one per probed host).
func SiblingDiff ¶
func SiblingDiff(reports []TargetReportLike) []SiblingDriftRow
SiblingDiff returns one SiblingDriftRow per Image that appears with a differing (Tag, Digest) tuple across two or more targets.
Inputs: the per-target reports produced by per-host Diff(). Output: deterministic order — by Image asc, then by Variant.Target asc. Empty result (nil slice) when fewer than 2 targets, or when no Image shows cross-host drift.
Rules:
- For each Image observed in any target's Diffs[].Runtime (regardless of Status — Match, OnlyRuntime, TagDrift, etc.), collect (Target, Tag, Digest, ...).
- If two or more variants for the same Image differ in (Tag, Digest), emit one row.
- Variants WITHOUT a Runtime (e.g. OnlySource rows) are skipped — only actually-running containers count for sibling-drift purposes.
- When Digest is empty on one side and non-empty on the other, treat as drift only if Tag also differs. (Same-tag/empty-digest-vs-real-digest is the "we don't have full info" case, not actionable drift.)
- When the same Image appears multiple times in one target (multiple containers), one representative variant is chosen (first by Container name ascending).
type SiblingVariant ¶
type SiblingVariant struct {
Target string `json:"target"` // the TargetReport.Target string (e.g. "ssh://piter")
Tag string `json:"tag"`
Digest string `json:"digest,omitempty"`
Container string `json:"container,omitempty"` // container name on that target
State string `json:"state,omitempty"`
}
SiblingVariant is one row inside a SiblingDriftRow — a single host's view of the image.
type Target ¶
type Target struct {
Raw string // original input, kept for diagnostics
Scheme string // "local" | "docker" | "ssh" (case-folded to lowercase)
Host string // ssh: hostname or alias as understood by ~/.ssh/config. docker/local: ""
User string // ssh-only; "" means default per ~/.ssh/config
Port int // ssh-only; 0 means default per ~/.ssh/config
}
Target identifies WHERE a probe should look. Scheme dispatch happens in Registry.Get.
func ParseTarget ¶
ParseTarget normalises a target URL into a Target.
Accepted forms:
"" → {Scheme:"local"}
"local://" → {Scheme:"local"}
"docker://" → {Scheme:"docker"}
"ssh://<host>" → {Scheme:"ssh", Host:"<host>"}
"ssh://<user>@<host>" → {Scheme:"ssh", User:"<user>", Host:"<host>"}
"ssh://<host>:<port>" → {Scheme:"ssh", Host:"<host>", Port:<port>}
"ssh://<user>@<host>:<port>"
Anything else returns an error wrapping ErrInvalidTarget.
Rules:
- Host MUST be non-empty for ssh.
- Host MUST be empty for local/docker (MVP: docker is local-socket only).
- Port MUST be in 1..65535 if specified.
- Scheme is case-folded to lowercase in the result.
Target.Raw is always set to the original input.
type TargetReportLike ¶
TargetReportLike is the minimal view of a per-host probe report that SiblingDiff needs. cmd/go-code's TargetReport implements this interface via TargetStr() and DiffsList() methods so that internal/fleet stays import-free of cmd/.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package docker provides a fleet.Probe implementation that discovers running containers via the local Docker Engine unix socket using raw HTTP/1.1.
|
Package docker provides a fleet.Probe implementation that discovers running containers via the local Docker Engine unix socket using raw HTTP/1.1. |
|
Package ssh provides a fleet.Probe implementation that discovers running containers on a remote host via the system `ssh` binary and `docker ps`.
|
Package ssh provides a fleet.Probe implementation that discovers running containers on a remote host via the system `ssh` binary and `docker ps`. |
|
internal/fleet/upstream/client.go
|
internal/fleet/upstream/client.go |