cmd/hakurei/print: use new store interface
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m15s
Test / Hakurei (push) Successful in 3m11s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m11s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m15s
Test / Hakurei (push) Successful in 3m11s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m11s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
This removes the final uses of the compat interfaces. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -20,7 +20,6 @@ import (
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/env"
|
||||
"hakurei.app/internal/outcome"
|
||||
"hakurei.app/internal/store"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
@@ -340,7 +339,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||
var sc hst.Paths
|
||||
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
|
||||
printPs(os.Stdout, time.Now().UTC(), store.NewMulti(msg, sc.SharePath), flagShort, flagJSON)
|
||||
printPs(msg, os.Stdout, time.Now().UTC(), outcome.NewStore(&sc), flagShort, flagJSON)
|
||||
return errSuccess
|
||||
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -170,52 +169,52 @@ func printShowInstance(
|
||||
}
|
||||
|
||||
// printPs writes a representation of active instances to output.
|
||||
func printPs(output io.Writer, now time.Time, s store.Compat, short, flagJSON bool) {
|
||||
var entries map[hst.ID]*hst.State
|
||||
if e, err := store.Join(s); err != nil {
|
||||
log.Fatalf("cannot join store: %v", err)
|
||||
} else {
|
||||
entries = e
|
||||
func printPs(msg message.Msg, output io.Writer, now time.Time, s *store.Store, short, flagJSON bool) {
|
||||
f := func(a func(eh *store.EntryHandle)) {
|
||||
entries, copyError := s.All()
|
||||
for eh := range entries {
|
||||
a(eh)
|
||||
}
|
||||
if err := copyError(); err != nil {
|
||||
msg.GetLogger().Println(getMessage("cannot iterate over store:", err))
|
||||
}
|
||||
}
|
||||
|
||||
if !short && flagJSON {
|
||||
es := slices.Collect(maps.Values(entries))
|
||||
slices.SortFunc(es, func(a, b *hst.State) int { return bytes.Compare(a.ID[:], b.ID[:]) })
|
||||
encodeJSON(log.Fatal, output, short, es)
|
||||
if short { // short output requires identifier only
|
||||
var identifiers []*hst.ID
|
||||
f(func(eh *store.EntryHandle) {
|
||||
if _, err := eh.Load(nil); err != nil { // passes through decode error
|
||||
msg.GetLogger().Println(getMessage("cannot validate state entry header:", err))
|
||||
return
|
||||
}
|
||||
identifiers = append(identifiers, &eh.ID)
|
||||
})
|
||||
slices.SortFunc(identifiers, func(a, b *hst.ID) int { return bytes.Compare(a[:], b[:]) })
|
||||
|
||||
if flagJSON {
|
||||
encodeJSON(log.Fatal, output, short, identifiers)
|
||||
} else {
|
||||
for _, id := range identifiers {
|
||||
mustPrintln(output, shortIdentifier(id))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// sort state entries by id string to ensure consistency between runs
|
||||
exp := make([]*expandedStateEntry, 0, len(entries))
|
||||
for id, instance := range entries {
|
||||
// gracefully skip nil states
|
||||
if instance == nil {
|
||||
log.Printf("got invalid state entry %s", id.String())
|
||||
continue
|
||||
// long output requires full instance state
|
||||
var instances []*hst.State
|
||||
f(func(eh *store.EntryHandle) {
|
||||
var state hst.State
|
||||
if _, err := eh.Load(&state); err != nil { // passes through decode error
|
||||
msg.GetLogger().Println(getMessage("cannot load state entry:", err))
|
||||
return
|
||||
}
|
||||
instances = append(instances, &state)
|
||||
})
|
||||
slices.SortFunc(instances, func(a, b *hst.State) int { return bytes.Compare(a.ID[:], b.ID[:]) })
|
||||
|
||||
// gracefully skip inconsistent states
|
||||
if id != instance.ID {
|
||||
log.Printf("possible store corruption: entry %s has id %s",
|
||||
id.String(), instance.ID.String())
|
||||
continue
|
||||
}
|
||||
exp = append(exp, &expandedStateEntry{s: id.String(), State: instance})
|
||||
}
|
||||
slices.SortFunc(exp, func(a, b *expandedStateEntry) int { return a.Time.Compare(b.Time) })
|
||||
|
||||
if short {
|
||||
if flagJSON {
|
||||
v := make([]string, len(exp))
|
||||
for i, e := range exp {
|
||||
v[i] = e.s
|
||||
}
|
||||
encodeJSON(log.Fatal, output, short, v)
|
||||
} else {
|
||||
for _, e := range exp {
|
||||
mustPrintln(output, shortIdentifierString(e.s))
|
||||
}
|
||||
}
|
||||
if flagJSON {
|
||||
encodeJSON(log.Fatal, output, short, instances)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -223,33 +222,21 @@ func printPs(output io.Writer, now time.Time, s store.Compat, short, flagJSON bo
|
||||
defer t.MustFlush()
|
||||
|
||||
t.Println("\tInstance\tPID\tApplication\tUptime")
|
||||
for _, e := range exp {
|
||||
if len(e.s) != 1<<5 {
|
||||
// unreachable
|
||||
log.Printf("possible store corruption: invalid instance string %s", e.s)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, instance := range instances {
|
||||
as := "(No configuration information)"
|
||||
if e.Config != nil {
|
||||
as = strconv.Itoa(e.Config.Identity)
|
||||
id := e.Config.ID
|
||||
if instance.Config != nil {
|
||||
as = strconv.Itoa(instance.Config.Identity)
|
||||
id := instance.Config.ID
|
||||
if id == "" {
|
||||
id = "app.hakurei." + shortIdentifierString(e.s)
|
||||
id = "app.hakurei." + shortIdentifier(&instance.ID)
|
||||
}
|
||||
as += " (" + id + ")"
|
||||
}
|
||||
t.Printf("\t%s\t%d\t%s\t%s\n",
|
||||
shortIdentifierString(e.s), e.PID, as, now.Sub(e.Time).Round(time.Second).String())
|
||||
shortIdentifier(&instance.ID), instance.PID, as, now.Sub(instance.Time).Round(time.Second).String())
|
||||
}
|
||||
}
|
||||
|
||||
// expandedStateEntry stores [hst.State] alongside a string representation of its [hst.ID].
|
||||
type expandedStateEntry struct {
|
||||
s string
|
||||
*hst.State
|
||||
}
|
||||
|
||||
// newPrinter returns a configured, wrapped [tabwriter.Writer].
|
||||
func newPrinter(output io.Writer) *tp { return &tp{tabwriter.NewWriter(output, 0, 1, 4, ' ', 0)} }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/store"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -18,13 +20,30 @@ var (
|
||||
0x4c, 0xf0, 0x73, 0xbd,
|
||||
0xb4, 0x6e, 0xb5, 0xc1,
|
||||
}
|
||||
testState = &hst.State{
|
||||
testState = hst.State{
|
||||
ID: testID,
|
||||
PID: 0xcafebabe,
|
||||
ShimPID: 0xdeadbeef,
|
||||
Config: hst.Template(),
|
||||
Time: testAppTime,
|
||||
}
|
||||
testStateSmall = hst.State{
|
||||
ID: (hst.ID)(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
|
||||
PID: 0xbeef,
|
||||
ShimPID: 0xcafe,
|
||||
Config: &hst.Config{
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EPulse),
|
||||
Identity: 1,
|
||||
Container: &hst.ContainerConfig{
|
||||
Shell: check.MustAbs("/bin/sh"),
|
||||
Home: check.MustAbs("/data/data/uk.gensokyo.cat"),
|
||||
Path: check.MustAbs("/usr/bin/cat"),
|
||||
Args: []string{"cat"},
|
||||
Flags: hst.FUserns,
|
||||
},
|
||||
},
|
||||
Time: time.Unix(0, 0xdeadbeef).UTC(),
|
||||
}
|
||||
testTime = time.Unix(3752, 1).UTC()
|
||||
testAppTime = time.Unix(0, 9).UTC()
|
||||
)
|
||||
@@ -133,7 +152,7 @@ Session bus
|
||||
|
||||
`, false},
|
||||
|
||||
{"instance", testState, hst.Template(), false, false, `State
|
||||
{"instance", &testState, hst.Template(), false, false, `State
|
||||
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
|
||||
Uptime: 1h2m32s
|
||||
|
||||
@@ -173,7 +192,7 @@ System bus
|
||||
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
||||
|
||||
`, true},
|
||||
{"instance pd", testState, new(hst.Config), false, false, `Error: configuration missing container state!
|
||||
{"instance pd", &testState, new(hst.Config), false, false, `Error: configuration missing container state!
|
||||
|
||||
State
|
||||
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
|
||||
@@ -187,7 +206,7 @@ App
|
||||
|
||||
{"json nil", nil, nil, false, true, `null
|
||||
`, true},
|
||||
{"json instance", testState, nil, false, true, `{
|
||||
{"json instance", &testState, nil, false, true, `{
|
||||
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
|
||||
"pid": 3405691582,
|
||||
"shim_pid": 3735928559,
|
||||
@@ -515,41 +534,27 @@ func TestPrintPs(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
entries map[hst.ID]*hst.State
|
||||
data []hst.State
|
||||
short, json bool
|
||||
want string
|
||||
want, log string
|
||||
}{
|
||||
{"no entries", make(map[hst.ID]*hst.State), false, false, " Instance PID Application Uptime\n"},
|
||||
{"no entries short", make(map[hst.ID]*hst.State), true, false, ""},
|
||||
{"nil instance", map[hst.ID]*hst.State{testID: nil}, false, false, " Instance PID Application Uptime\n"},
|
||||
{"state corruption", map[hst.ID]*hst.State{hst.ID{}: testState}, false, false, " Instance PID Application Uptime\n"},
|
||||
{"no entries", []hst.State{}, false, false, " Instance PID Application Uptime\n", ""},
|
||||
{"no entries short", []hst.State{}, true, false, "", ""},
|
||||
|
||||
{"valid pd", map[hst.ID]*hst.State{testID: {ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, ` Instance PID Application Uptime
|
||||
4cf073bd 256 0 (app.hakurei.4cf073bd) 1h2m32s
|
||||
`},
|
||||
{"invalid config", []hst.State{{ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, " Instance PID Application Uptime\n", "check: configuration missing container state\n"},
|
||||
|
||||
{"valid", map[hst.ID]*hst.State{testID: testState}, false, false, ` Instance PID Application Uptime
|
||||
{"valid", []hst.State{testStateSmall, testState}, false, false, ` Instance PID Application Uptime
|
||||
4cf073bd 3405691582 9 (org.chromium.Chromium) 1h2m32s
|
||||
`},
|
||||
{"valid short", map[hst.ID]*hst.State{testID: testState}, true, false, "4cf073bd\n"},
|
||||
{"valid json", map[hst.ID]*hst.State{testID: testState, (hst.ID)(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))): {
|
||||
ID: (hst.ID)(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
|
||||
PID: 0xbeef,
|
||||
ShimPID: 0xcafe,
|
||||
Config: &hst.Config{
|
||||
ID: "uk.gensokyo.cat",
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EPulse),
|
||||
Identity: 1,
|
||||
Container: &hst.ContainerConfig{
|
||||
Shell: check.MustAbs("/bin/sh"),
|
||||
Home: check.MustAbs("/data/data/uk.gensokyo.cat"),
|
||||
Path: check.MustAbs("/usr/bin/cat"),
|
||||
Args: []string{"cat"},
|
||||
Flags: hst.FUserns,
|
||||
},
|
||||
},
|
||||
Time: time.Unix(0, 0xdeadbeef).UTC(),
|
||||
}}, false, true, `[
|
||||
aaaaaaaa 48879 1 (app.hakurei.aaaaaaaa) 1h2m28s
|
||||
`, ""},
|
||||
{"valid single", []hst.State{testState}, false, false, ` Instance PID Application Uptime
|
||||
4cf073bd 3405691582 9 (org.chromium.Chromium) 1h2m32s
|
||||
`, ""},
|
||||
|
||||
{"valid short", []hst.State{testStateSmall, testState}, true, false, "4cf073bd\naaaaaaaa\n", ""},
|
||||
{"valid short single", []hst.State{testState}, true, false, "4cf073bd\n", ""},
|
||||
|
||||
{"valid json", []hst.State{testState, testStateSmall}, false, true, `[
|
||||
{
|
||||
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
|
||||
"pid": 3405691582,
|
||||
@@ -707,7 +712,7 @@ func TestPrintPs(t *testing.T) {
|
||||
"instance": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"pid": 48879,
|
||||
"shim_pid": 51966,
|
||||
"id": "uk.gensokyo.cat",
|
||||
"id": "",
|
||||
"enablements": {
|
||||
"wayland": true,
|
||||
"pulse": true
|
||||
@@ -729,30 +734,44 @@ func TestPrintPs(t *testing.T) {
|
||||
"time": "1970-01-01T00:00:03.735928559Z"
|
||||
}
|
||||
]
|
||||
`},
|
||||
{"valid short json", map[hst.ID]*hst.State{testID: testState}, true, true, `["8e2c76b066dabe574cf073bdb46eb5c1"]
|
||||
`},
|
||||
`, ""},
|
||||
{"valid short json", []hst.State{testStateSmall, testState}, true, true, `["8e2c76b066dabe574cf073bdb46eb5c1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
|
||||
`, ""},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s := store.New(check.MustAbs(t.TempDir()).Append("store"))
|
||||
for i := range tc.data {
|
||||
if h, err := s.Handle(tc.data[i].Identity); err != nil {
|
||||
t.Fatalf("Handle: error = %v", err)
|
||||
} else {
|
||||
var unlock func()
|
||||
if unlock, err = h.Lock(); err != nil {
|
||||
t.Fatalf("Lock: error = %v", err)
|
||||
}
|
||||
_, err = h.Save(&tc.data[i])
|
||||
unlock()
|
||||
if err != nil {
|
||||
t.Fatalf("Save: error = %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store must not be written to beyond this point
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
output := new(strings.Builder)
|
||||
printPs(output, testTime, stubStore(tc.entries), tc.short, tc.json)
|
||||
if got := output.String(); got != tc.want {
|
||||
t.Errorf("printPs: got\n%s\nwant\n%s",
|
||||
got, tc.want)
|
||||
var printBuf, logBuf bytes.Buffer
|
||||
msg := message.NewMsg(log.New(&logBuf, "check: ", 0))
|
||||
msg.SwapVerbose(true)
|
||||
printPs(msg, &printBuf, testTime, s, tc.short, tc.json)
|
||||
if got := printBuf.String(); got != tc.want {
|
||||
t.Errorf("printPs:\n%s\nwant\n%s", got, tc.want)
|
||||
return
|
||||
}
|
||||
if got := logBuf.String(); got != tc.log {
|
||||
t.Errorf("msg:\n%s\nwant\n%s", got, tc.log)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// stubStore implements [state.Store] and returns test samples via [state.Joiner].
|
||||
type stubStore map[hst.ID]*hst.State
|
||||
|
||||
func (s stubStore) Join() (map[hst.ID]*hst.State, error) { return s, nil }
|
||||
func (s stubStore) Do(int, func(c store.Cursor)) (bool, error) { panic("unreachable") }
|
||||
func (s stubStore) List() ([]int, error) { panic("unreachable") }
|
||||
func (s stubStore) Close() error { return nil }
|
||||
|
||||
Reference in New Issue
Block a user