ananke/internal/state/intent_parse.go

56 lines
1.6 KiB
Go
Raw Normal View History

package state
import (
"fmt"
"strings"
"time"
)
// ParseIntentOutput parses `ananke intent` CLI output from local/remote commands.
// Signature: ParseIntentOutput(raw string) (Intent, error)
// Why: Startup/shutdown coordination depends on intent state being interpreted
// consistently from command output so remote peers and local orchestration can
// share one durable control-plane signal.
func ParseIntentOutput(raw string) (Intent, error) {
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
idx := strings.Index(line, "intent=")
if idx < 0 {
continue
}
payload := strings.TrimSpace(line[idx:])
fields := strings.Fields(payload)
stateValue := strings.TrimSpace(strings.TrimPrefix(fields[0], "intent="))
if stateValue == "" || stateValue == "none" {
return Intent{}, nil
}
in := Intent{State: stateValue}
if strings.Contains(payload, `reason="`) {
parts := strings.SplitN(payload, `reason="`, 2)
if end := strings.Index(parts[1], `"`); end >= 0 {
in.Reason = parts[1][:end]
}
}
for _, field := range fields[1:] {
if strings.HasPrefix(field, "source=") {
in.Source = strings.TrimSpace(strings.TrimPrefix(field, "source="))
}
if strings.HasPrefix(field, "updated_at=") {
ts := strings.TrimSpace(strings.TrimPrefix(field, "updated_at="))
if ts != "" {
parsed, err := time.Parse(time.RFC3339, ts)
if err != nil {
return Intent{}, fmt.Errorf("parse updated_at %q: %w", ts, err)
}
in.UpdatedAt = parsed
}
}
}
return in, nil
}
return Intent{}, fmt.Errorf("intent output missing intent= token")
}