Skip to content

Commit 341f335

Browse files
authored
Merge pull request #201 from adrianreber/2026-02-23-inspect
Refactor inspect to use single data collection phase
2 parents c503439 + 059201e commit 341f335

3 files changed

Lines changed: 770 additions & 330 deletions

File tree

internal/json.go

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,13 @@ type DisplayNode struct {
8888
MAC string `json:"mac,omitempty"`
8989
CheckpointSize CheckpointSize `json:"checkpoint_size"`
9090
CriuDumpStatistics *StatsNode `json:"statistics,omitempty"`
91+
Metadata *MetadataNode `json:"metadata,omitempty"`
9192
ProcessTree *PsNode `json:"process_tree,omitempty"`
9293
FileDescriptors []FdNode `json:"file_descriptors,omitempty"`
9394
Sockets []SkNode `json:"sockets,omitempty"`
9495
Mounts []MountNode `json:"mounts,omitempty"`
96+
// Internal fields for tree rendering (not serialized to JSON)
97+
checkpointFilePath string
9598
}
9699

97100
type MountNode struct {
@@ -100,22 +103,32 @@ type MountNode struct {
100103
Source string `json:"source"`
101104
}
102105

103-
func RenderJSONView(tasks []Task) error {
106+
type MetadataNode struct {
107+
PodName string `json:"pod_name,omitempty"`
108+
KubernetesNamespace string `json:"kubernetes_namespace,omitempty"`
109+
Annotations map[string]string `json:"annotations,omitempty"`
110+
}
111+
112+
// CollectCheckpointData collects all checkpoint data into DisplayNode structures.
113+
// This is the single source of truth for checkpoint data that can be rendered
114+
// in multiple formats (JSON, tree, etc.).
115+
func CollectCheckpointData(tasks []Task) ([]DisplayNode, error) {
104116
var result []DisplayNode
105117

106118
for _, task := range tasks {
107119
info, err := getCheckpointInfo(task)
108120
if err != nil {
109-
return err
121+
return nil, err
110122
}
111123

112124
node := DisplayNode{
113-
ContainerName: info.containerInfo.Name,
114-
Image: info.configDump.RootfsImageName,
115-
ID: info.configDump.ID,
116-
Runtime: info.configDump.OCIRuntime,
117-
Created: info.containerInfo.Created,
118-
Engine: info.containerInfo.Engine,
125+
ContainerName: info.containerInfo.Name,
126+
Image: info.configDump.RootfsImageName,
127+
ID: info.configDump.ID,
128+
Runtime: info.configDump.OCIRuntime,
129+
Created: info.containerInfo.Created,
130+
Engine: info.containerInfo.Engine,
131+
checkpointFilePath: task.CheckpointFilePath,
119132
}
120133

121134
if !info.configDump.CheckpointedAt.IsZero() {
@@ -150,7 +163,7 @@ func RenderJSONView(tasks []Task) error {
150163
if Stats {
151164
dumpStats, err := crit.GetDumpStats(task.OutputDir)
152165
if err != nil {
153-
return fmt.Errorf("failed to get dump statistics: %w", err)
166+
return nil, fmt.Errorf("failed to get dump statistics: %w", err)
154167
}
155168

156169
statsNode := StatsNode{
@@ -165,49 +178,73 @@ func RenderJSONView(tasks []Task) error {
165178
node.CriuDumpStatistics = &statsNode
166179
}
167180

181+
if Metadata {
182+
metadataNode := MetadataNode{}
183+
if info.containerInfo.Pod != "" {
184+
metadataNode.PodName = info.containerInfo.Pod
185+
}
186+
if info.containerInfo.Namespace != "" {
187+
metadataNode.KubernetesNamespace = info.containerInfo.Namespace
188+
}
189+
if len(info.specDump.Annotations) > 0 {
190+
metadataNode.Annotations = info.specDump.Annotations
191+
}
192+
node.Metadata = &metadataNode
193+
}
194+
195+
checkpointDirectory := filepath.Join(task.OutputDir, "checkpoint")
196+
168197
if PsTree {
169-
psTree, err := crit.New(nil, nil, filepath.Join(task.OutputDir, "checkpoint"), false, false).ExplorePs()
198+
psTree, err := crit.New(nil, nil, checkpointDirectory, false, false).ExplorePs()
170199
if err != nil {
171-
return fmt.Errorf("failed to get process tree: %w", err)
200+
return nil, fmt.Errorf("failed to get process tree: %w", err)
172201
}
173202

174203
psTreeNode, err := buildJSONPsTree(psTree, task.OutputDir)
175204
if err != nil {
176-
return fmt.Errorf("failed to get process tree: %w", err)
205+
return nil, fmt.Errorf("failed to get process tree: %w", err)
177206
}
178207

179208
node.ProcessTree = &psTreeNode
180209
}
181210

182211
if Files {
183-
fds, err := crit.New(nil, nil, filepath.Join(task.OutputDir, "checkpoint"), false, false).ExploreFds()
212+
fds, err := crit.New(nil, nil, checkpointDirectory, false, false).ExploreFds()
184213
if err != nil {
185-
return fmt.Errorf("failed to get file descriptors: %w", err)
214+
return nil, fmt.Errorf("failed to get file descriptors: %w", err)
186215
}
187216

188217
node.FileDescriptors = buildJSONFds(fds)
189218
}
190219

191220
if Sockets {
192-
fds, err := crit.New(nil, nil, filepath.Join(task.OutputDir, "checkpoint"), false, false).ExploreSk()
221+
sks, err := crit.New(nil, nil, checkpointDirectory, false, false).ExploreSk()
193222
if err != nil {
194-
return fmt.Errorf("failed to get sockets: %w", err)
223+
return nil, fmt.Errorf("failed to get sockets: %w", err)
195224
}
196225

197-
node.Sockets, err = buildJSONSks(fds)
226+
node.Sockets, err = buildJSONSks(sks)
198227
if err != nil {
199-
return fmt.Errorf("failed to build sockets: %w", err)
228+
return nil, fmt.Errorf("failed to build sockets: %w", err)
200229
}
201230
}
202231

203232
if Mounts {
204-
specDump := info.specDump
205-
node.Mounts = buildJSONMounts(specDump)
233+
node.Mounts = buildJSONMounts(info.specDump)
206234
}
207235

208236
result = append(result, node)
209237
}
210238

239+
return result, nil
240+
}
241+
242+
func RenderJSONView(tasks []Task) error {
243+
result, err := CollectCheckpointData(tasks)
244+
if err != nil {
245+
return err
246+
}
247+
211248
jsonData, err := json.MarshalIndent(result, "", " ")
212249
if err != nil {
213250
return err
@@ -269,7 +306,7 @@ func buildJSONPsNode(psTree *crit.PsTree, checkpointOutputDir string) (PsNode, e
269306
if PsTreeCmd {
270307
cmdline, err := getCmdline(checkpointOutputDir, psTree.PID)
271308
if err != nil {
272-
return PsNode{}, err
309+
return PsNode{}, fmt.Errorf("failed to process command line arguments: %w", err)
273310
}
274311
node.Cmdline = cmdline
275312
}

0 commit comments

Comments
 (0)