Answer the review of hq issue 348's catalogue change
mesh/merge-gate pass: builds dnsmasq, docker, netcheck → ace, g14, novox, shanks; no bus step; 2 wait(s) for a person; every machine composes with the chan…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds dnsmasq, docker, netcheck → ace, g14, novox, shanks; no bus step; 2 wait(s) for a person; every machine composes with the chan…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
- The dnsmasq watch restarts only a unit that runs, without waiting for its start; a unit stopped, starting or failed is held and said (C2). - dnsmasq_resolve asks only an address the resolver listens on (C3). - Tests for the watch loop keeping its budget, a watch that wrote nothing or stopped looking, and a unit not running (C4). - A restart is remembered as said only once emitted, restarts made before the bundle started are said, and whether one helped is said (C5). - docker_events reads the runtime's whole buffer: complete when it is not full or reaches past the window, never a quiet machine read as lost (C6). - netcheck: refused read with errors.Is; tests for addresses and ports, and for a container named as docker knows it.
This commit is contained in:
@@ -1011,15 +1011,24 @@ func (c *Client) eventsAt(ctx context.Context, now time.Time, minutes int, kind
|
||||
return nil, fmt.Errorf("type %q: container, image, network, volume, daemon, plugin or builder", kind)
|
||||
}
|
||||
}
|
||||
args := []string{"events", "--since", fmt.Sprintf("%dm", minutes), "--until", "0s", "--format", "{{json .}}"}
|
||||
// The whole of what the runtime holds, from the epoch: its buffer, which says where its history really
|
||||
// begins and whether it is full; the window is cut from it here.
|
||||
args := []string{"events", "--since", "0", "--until", "0s", "--format", "{{json .}}"}
|
||||
out, err := c.docker(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, err := jsonLines[runtimeEvent](out)
|
||||
all, err := jsonLines[runtimeEvent](out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start := now.Add(-time.Duration(minutes) * time.Minute)
|
||||
raw := make([]runtimeEvent, 0, len(all))
|
||||
for _, e := range all {
|
||||
if !time.Unix(0, e.TimeNano).Before(start) {
|
||||
raw = append(raw, e)
|
||||
}
|
||||
}
|
||||
// An exec's command line is shown without the secrets it carried (novox/hq issue 282): this answer
|
||||
// is read by agents and kept in their transcripts, which would make it a second copy of the leak.
|
||||
envs := &envCache{c: c, ctx: ctx}
|
||||
@@ -1057,41 +1066,47 @@ func (c *Client) eventsAt(ctx context.Context, now time.Time, minutes int, kind
|
||||
events = events[len(events)-limit:]
|
||||
}
|
||||
answer := map[string]any{"minutes": minutes, "count": total, "shown": len(events), "events": events,
|
||||
"held": len(raw)}
|
||||
"held": len(all)}
|
||||
if len(redacted) > 0 {
|
||||
answer["redacted"] = sortedSet(redacted)
|
||||
answer["leak"] = "exec command lines carried secrets, which the runtime keeps in its events; docker_secrets_in_events names them (novox/hq issue 282)"
|
||||
}
|
||||
for k, v := range historyReach(raw, now, minutes) {
|
||||
for k, v := range historyReach(all, start) {
|
||||
answer[k] = v
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
// historyGap is how much later than the window's start the runtime's oldest held event may be and the
|
||||
// window still read as covered: events are not made every second.
|
||||
const historyGap = 2 * time.Minute
|
||||
// RuntimeEventsKept is how many events the runtime keeps in memory (moby's events.eventsLimit): a buffer
|
||||
// holding fewer has every event since the runtime started; a full one has lost what came before its oldest.
|
||||
const RuntimeEventsKept = 256
|
||||
|
||||
// historyReach says where the runtime's history begins when it begins after the window asked for: the
|
||||
// oldest event it still holds, and that nothing before it can be answered from here.
|
||||
func historyReach(raw []runtimeEvent, now time.Time, minutes int) map[string]any {
|
||||
start := now.Add(-time.Duration(minutes) * time.Minute)
|
||||
if len(raw) == 0 {
|
||||
return map[string]any{"complete": false,
|
||||
"history": "the runtime holds no events at all for this window: it keeps only its last events in memory, so this says nothing about what happened before now — ask the service manager's journal for the docker unit"}
|
||||
// historyReach says how far back the runtime's buffer reaches, read from all of it: complete for a window
|
||||
// that starts at or after the oldest event a full buffer holds, or for any window when the buffer is not
|
||||
// full — then it holds everything since the runtime started. Otherwise where the history begins, and that
|
||||
// nothing before it can be answered from here.
|
||||
func historyReach(all []runtimeEvent, start time.Time) map[string]any {
|
||||
if len(all) == 0 {
|
||||
return map[string]any{"complete": true, "oldest_held": nil,
|
||||
"history": "the runtime holds no events at all: none happened since it started"}
|
||||
}
|
||||
oldest := time.Unix(0, raw[0].TimeNano)
|
||||
for _, e := range raw {
|
||||
oldest := time.Unix(0, all[0].TimeNano)
|
||||
for _, e := range all {
|
||||
if t := time.Unix(0, e.TimeNano); t.Before(oldest) {
|
||||
oldest = t
|
||||
}
|
||||
}
|
||||
if oldest.Sub(start) <= historyGap {
|
||||
return map[string]any{"complete": true, "oldest_held": oldest.UTC().Format(time.RFC3339)}
|
||||
at := oldest.UTC().Format(time.RFC3339)
|
||||
if len(all) < RuntimeEventsKept {
|
||||
return map[string]any{"complete": true, "oldest_held": at,
|
||||
"history": fmt.Sprintf("the runtime holds %d events, fewer than the %d it keeps: every event since it started", len(all), RuntimeEventsKept)}
|
||||
}
|
||||
return map[string]any{"complete": false, "oldest_held": oldest.UTC().Format(time.RFC3339),
|
||||
if !oldest.After(start) {
|
||||
return map[string]any{"complete": true, "oldest_held": at}
|
||||
}
|
||||
return map[string]any{"complete": false, "oldest_held": at,
|
||||
"history": fmt.Sprintf("the runtime keeps only its last %d events in memory and its oldest is from %s; the window asked starts at %s, so what happened before %s is gone from here, not absent — the images' health checks fill the history with exec events (hq research 032)",
|
||||
len(raw), oldest.UTC().Format(time.RFC3339), start.UTC().Format(time.RFC3339), oldest.UTC().Format("15:04:05Z"))}
|
||||
len(all), at, start.UTC().Format(time.RFC3339), oldest.UTC().Format("15:04:05Z"))}
|
||||
}
|
||||
|
||||
func sortedSet(set map[string]bool) []string {
|
||||
|
||||
Reference in New Issue
Block a user