package client import ( "fmt" "io" "github.com/m3tam3re/agent-lib/internal/version" ) // StatusEntry names one item in a status section. type StatusEntry struct { Item string `json:"item"` Reason string `json:"reason,omitempty"` } // StatusReport is the machine-readable local-state report. type StatusReport struct { Version string `json:"agent_lib_version"` DeployedRev string `json:"deployed_revision"` RepoRev string `json:"repository_revision"` InSync bool `json:"in_sync"` Drift bool `json:"problematic_drift"` Pending []StatusEntry `json:"pending_changes,omitempty"` Skipped []StatusEntry `json:"skipped_items,omitempty"` Unrecognized []StatusEntry `json:"unrecognized_items,omitempty"` } // Status reports the local state without mutating anything. Drift is true // when local modifications block repository updates (or occupy names the // repository needs) — the state headless drift checks flag with a non-zero // exit code. func Status(opts SyncOptions) (*StatusReport, error) { p, err := prepare(opts) if err != nil { return nil, err } report := &StatusReport{ Version: version.Version, DeployedRev: p.manifest.Revision, RepoRev: p.pull.Rev, } for _, e := range p.plan { item := e.Type + "/" + e.Name switch e.Action { case ActDeploy, ActUpdate, ActRemove: report.Pending = append(report.Pending, StatusEntry{Item: item, Reason: string(e.Action)}) case ActSkip: blocked := true if rec := p.manifest.Items[manifestKey(e.Type, e.Name)]; rec != nil && rec.Hash == e.NewHash { blocked = false } if blocked { report.Drift = true } report.Skipped = append(report.Skipped, StatusEntry{Item: item, Reason: e.Reason}) case ActLeave: report.Unrecognized = append(report.Unrecognized, StatusEntry{Item: item, Reason: e.Reason}) } } report.InSync = report.DeployedRev == report.RepoRev && len(report.Pending) == 0 return report, nil } // PrintStatus renders the human-readable report and returns the drift // verdict so the caller can set the exit code. func PrintStatus(w io.Writer, r *StatusReport) bool { fmt.Fprintf(w, "agent-lib %s\n", r.Version) fmt.Fprintf(w, "deployed revision: %s\n", revOrNone(r.DeployedRev)) fmt.Fprintf(w, "repository revision: %s\n", revOrNone(r.RepoRev)) if r.InSync { fmt.Fprintln(w, "state: in sync") } else { fmt.Fprintln(w, "state: out of sync") } printSection := func(title string, entries []StatusEntry) { if len(entries) == 0 { return } fmt.Fprintf(w, "%s:\n", title) for _, e := range entries { fmt.Fprintf(w, " %s", e.Item) if e.Reason != "" { fmt.Fprintf(w, " — %s", e.Reason) } fmt.Fprintln(w) } } printSection("pending changes", r.Pending) printSection("skipped (local modifications)", r.Skipped) printSection("unrecognized local items", r.Unrecognized) if r.Drift { fmt.Fprintln(w, "drift: local modifications block repository updates") } return r.Drift } func revOrNone(rev string) string { if rev == "" { return "(never synced)" } return rev }