Skip to content

Commit 2a4d478

Browse files
laozcsaschagrunert
authored andcommitted
Fixes invalid JSON in crictl info
containerd on Windows may not escape the return message which may result in invalid JSON in crictl info. Message from containerd: cni config load failed: no network config found in C:\Program Files \containerd\cni\conf: cni plugin not initialized: failed to load cni config Cherry-picked: 88df400 Signed-off-by: Sascha Grunert <[email protected]>
1 parent 71030a7 commit 2a4d478

File tree

2 files changed

+160
-14
lines changed

2 files changed

+160
-14
lines changed

cmd/crictl/util.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -289,37 +289,54 @@ func outputStatusInfo(status, handlers string, info map[string]string, format st
289289
}
290290
sort.Strings(keys)
291291

292-
jsonInfo := "{" + "\"status\":" + status + ","
292+
infoMap := map[string]interface{}{}
293+
294+
if status != "" {
295+
var statusVal map[string]interface{}
296+
err := json.Unmarshal([]byte(status), &statusVal)
297+
if err != nil {
298+
return err
299+
}
300+
infoMap["status"] = statusVal
301+
}
302+
293303
if handlers != "" {
294-
jsonInfo += "\"runtimeHandlers\":" + handlers + ","
304+
var handlersVal []*interface{}
305+
err := json.Unmarshal([]byte(handlers), &handlersVal)
306+
if err != nil {
307+
return err
308+
}
309+
if handlersVal != nil {
310+
infoMap["runtimeHandlers"] = handlersVal
311+
}
295312
}
313+
296314
for _, k := range keys {
297-
var res interface{}
298-
// We attempt to convert key into JSON if possible else use it directly
299-
if err := json.Unmarshal([]byte(info[k]), &res); err != nil {
300-
jsonInfo += "\"" + k + "\"" + ":" + "\"" + info[k] + "\","
301-
} else {
302-
jsonInfo += "\"" + k + "\"" + ":" + info[k] + ","
303-
}
315+
var genericVal map[string]interface{}
316+
json.Unmarshal([]byte(info[k]), &genericVal)
317+
infoMap[k] = genericVal
318+
}
319+
320+
jsonInfo, err := json.Marshal(infoMap)
321+
if err != nil {
322+
return err
304323
}
305-
jsonInfo = jsonInfo[:len(jsonInfo)-1]
306-
jsonInfo += "}"
307324

308325
switch format {
309326
case "yaml":
310-
yamlInfo, err := yaml.JSONToYAML([]byte(jsonInfo))
327+
yamlInfo, err := yaml.JSONToYAML(jsonInfo)
311328
if err != nil {
312329
return err
313330
}
314331
fmt.Println(string(yamlInfo))
315332
case "json":
316333
var output bytes.Buffer
317-
if err := json.Indent(&output, []byte(jsonInfo), "", " "); err != nil {
334+
if err := json.Indent(&output, jsonInfo, "", " "); err != nil {
318335
return err
319336
}
320337
fmt.Println(output.String())
321338
case "go-template":
322-
output, err := tmplExecuteRawJSON(tmplStr, jsonInfo)
339+
output, err := tmplExecuteRawJSON(tmplStr, string(jsonInfo))
323340
if err != nil {
324341
return err
325342
}

cmd/crictl/util_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"io"
21+
"os"
22+
"strings"
2023
"testing"
24+
25+
. "github.com/onsi/gomega"
2126
)
2227

2328
func TestNameFilterByRegex(t *testing.T) {
@@ -64,7 +69,131 @@ func TestNameFilterByRegex(t *testing.T) {
6469
if r != tc.isMatch {
6570
t.Errorf("expected matched to be %v; actual result is %v", tc.isMatch, r)
6671
}
72+
})
73+
}
74+
}
6775

76+
func TestOutputStatusInfo(t *testing.T) {
77+
const (
78+
statusResponse = `{"conditions":[
79+
{
80+
"message": "no network config found in C:\\Program Files",
81+
"reason": "NetworkPluginNotReady",
82+
"status": false,
83+
"type": "NetworkReady"
84+
}
85+
]}`
86+
handlerResponse = `[
87+
{
88+
"features": {
89+
"recursive_read_only_mounts": true
90+
},
91+
"name": "runc"
92+
},
93+
{
94+
"features": {
95+
"recursive_read_only_mounts": true,
96+
"user_namespaces": true
97+
},
98+
"name": "crun"
99+
}
100+
]`
101+
emptyResponse = ""
102+
)
103+
testCases := []struct {
104+
name string
105+
status string
106+
handlers string
107+
info map[string]string
108+
format string
109+
tmplStr string
110+
expectedOut string
111+
}{
112+
{
113+
name: "YAML format",
114+
status: statusResponse,
115+
handlers: handlerResponse,
116+
info: map[string]string{"key1": `{"foo": "bar"}`, "key2": `{"bar": "baz"}`},
117+
format: "yaml",
118+
tmplStr: "",
119+
expectedOut: "key1:\n foo: bar\nkey2:\n bar: baz\nruntimeHandlers:\n- features:\n recursive_read_only_mounts: true\n name: runc\n- features:\n recursive_read_only_mounts: true\n user_namespaces: true\n name: crun\nstatus:\n conditions:\n - message: no network config found in C:\\Program Files\n reason: NetworkPluginNotReady\n status: false\n type: NetworkReady",
120+
},
121+
{
122+
name: "YAML format with empty status response",
123+
status: emptyResponse,
124+
handlers: handlerResponse,
125+
format: "yaml",
126+
tmplStr: "",
127+
expectedOut: "runtimeHandlers:\n- features:\n recursive_read_only_mounts: true\n name: runc\n- features:\n recursive_read_only_mounts: true\n user_namespaces: true\n name: crun",
128+
},
129+
{
130+
name: "YAML format with empty handlers response",
131+
status: statusResponse,
132+
handlers: emptyResponse,
133+
format: "yaml",
134+
tmplStr: "",
135+
expectedOut: "status:\n conditions:\n - message: no network config found in C:\\Program Files\n reason: NetworkPluginNotReady\n status: false\n type: NetworkReady",
136+
},
137+
{
138+
name: "JSON format",
139+
status: statusResponse,
140+
handlers: handlerResponse,
141+
format: "json",
142+
tmplStr: "",
143+
expectedOut: "{\n \"runtimeHandlers\": [\n {\n \"features\": {\n \"recursive_read_only_mounts\": true\n },\n \"name\": \"runc\"\n },\n {\n \"features\": {\n \"recursive_read_only_mounts\": true,\n \"user_namespaces\": true\n },\n \"name\": \"crun\"\n }\n ],\n \"status\": {\n \"conditions\": [\n {\n \"message\": \"no network config found in C:\\\\Program Files\",\n \"reason\": \"NetworkPluginNotReady\",\n \"status\": false,\n \"type\": \"NetworkReady\"\n }\n ]\n }\n}",
144+
},
145+
{
146+
name: "Go template format",
147+
status: statusResponse,
148+
handlers: handlerResponse,
149+
format: "go-template",
150+
tmplStr: `NetworkReady: {{ (index .status.conditions 0).status }}`,
151+
expectedOut: "NetworkReady: false",
152+
},
153+
}
154+
155+
// Run tests
156+
for _, tc := range testCases {
157+
t.Run(tc.name, func(t *testing.T) {
158+
captureOutput := func(f func() error) (string, error) {
159+
var err error
160+
old := os.Stdout
161+
162+
r, w, _ := os.Pipe()
163+
os.Stdout = w
164+
defer func() {
165+
os.Stdout = old
166+
}()
167+
168+
err = f()
169+
if err != nil {
170+
return "", err
171+
}
172+
173+
err = w.Close()
174+
if err != nil {
175+
return "", err
176+
}
177+
178+
out, err := io.ReadAll(r)
179+
return strings.TrimRight(string(out), "\n"), err
180+
}
181+
182+
outStr, err := captureOutput(func() error {
183+
err := outputStatusInfo(tc.status, tc.handlers, tc.info, tc.format, tc.tmplStr)
184+
if err != nil {
185+
t.Errorf("Unexpected error: %v", err)
186+
}
187+
return nil
188+
})
189+
190+
if err != nil {
191+
Expect(err).To(BeNil())
192+
}
193+
194+
if outStr != tc.expectedOut {
195+
t.Errorf("Expected output:\n%s\nGot:\n%s", tc.expectedOut, outStr)
196+
}
68197
})
69198
}
70199
}

0 commit comments

Comments
 (0)