103 lines
3.0 KiB
Go
103 lines
3.0 KiB
Go
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
|
||
|
|
}
|