Skip to content

Commit 88df400

Browse files
committed
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
1 parent 7654680 commit 88df400

File tree

2 files changed

+162
-14
lines changed

2 files changed

+162
-14
lines changed

cmd/crictl/util.go

+29-14
Original file line numberDiff line numberDiff line change
@@ -245,37 +245,52 @@ func outputStatusInfo(status, handlers string, info map[string]string, format st
245245
}
246246
sort.Strings(keys)
247247

248-
jsonInfo := "{" + "\"status\":" + status + ","
248+
infoMap := map[string]interface{}{}
249+
250+
if status != "" {
251+
var statusVal map[string]interface{}
252+
err := json.Unmarshal([]byte(status), &statusVal)
253+
if err != nil {
254+
return err
255+
}
256+
infoMap["status"] = statusVal
257+
}
258+
249259
if handlers != "" {
250-
jsonInfo += "\"runtimeHandlers\":" + handlers + ","
260+
var handlersVal []*interface{}
261+
err := json.Unmarshal([]byte(handlers), &handlersVal)
262+
if err != nil {
263+
return err
264+
}
265+
if handlersVal != nil {
266+
infoMap["runtimeHandlers"] = handlersVal
267+
}
251268
}
269+
252270
for _, k := range keys {
253-
var res interface{}
254-
// We attempt to convert key into JSON if possible else use it directly
255-
if err := json.Unmarshal([]byte(info[k]), &res); err != nil {
256-
jsonInfo += "\"" + k + "\"" + ":" + "\"" + info[k] + "\","
257-
} else {
258-
jsonInfo += "\"" + k + "\"" + ":" + info[k] + ","
259-
}
271+
infoMap[k] = strings.Trim(info[k], "\"")
272+
}
273+
274+
jsonInfo, err := json.Marshal(infoMap)
275+
if err != nil {
276+
return err
260277
}
261-
jsonInfo = jsonInfo[:len(jsonInfo)-1]
262-
jsonInfo += "}"
263278

264279
switch format {
265280
case "yaml":
266-
yamlInfo, err := yaml.JSONToYAML([]byte(jsonInfo))
281+
yamlInfo, err := yaml.JSONToYAML(jsonInfo)
267282
if err != nil {
268283
return err
269284
}
270285
fmt.Println(string(yamlInfo))
271286
case "json":
272287
var output bytes.Buffer
273-
if err := json.Indent(&output, []byte(jsonInfo), "", " "); err != nil {
288+
if err := json.Indent(&output, jsonInfo, "", " "); err != nil {
274289
return err
275290
}
276291
fmt.Println(output.String())
277292
case "go-template":
278-
output, err := tmplExecuteRawJSON(tmplStr, jsonInfo)
293+
output, err := tmplExecuteRawJSON(tmplStr, string(jsonInfo))
279294
if err != nil {
280295
return err
281296
}

cmd/crictl/util_test.go

+133
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,135 @@ 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+
}
75+
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": "value1", "key2": "/var/lib"},
117+
format: "yaml",
118+
tmplStr: "",
119+
expectedOut: "key1: value1\nkey2: /var/lib\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+
info: map[string]string{"key1": "value1", "key2": "/var/lib"},
126+
format: "yaml",
127+
tmplStr: "",
128+
expectedOut: "key1: value1\nkey2: /var/lib\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",
129+
},
130+
{
131+
name: "YAML format with empty handlers response",
132+
status: statusResponse,
133+
handlers: emptyResponse,
134+
info: map[string]string{"key1": "value1", "key2": "/var/lib"},
135+
format: "yaml",
136+
tmplStr: "",
137+
expectedOut: "key1: value1\nkey2: /var/lib\nstatus:\n conditions:\n - message: no network config found in C:\\Program Files\n reason: NetworkPluginNotReady\n status: false\n type: NetworkReady",
138+
},
139+
{
140+
name: "JSON format",
141+
status: statusResponse,
142+
handlers: handlerResponse,
143+
info: map[string]string{"key1": "\"value1\"", "key2": "\"C:\\ProgramFiles\""},
144+
format: "json",
145+
tmplStr: "",
146+
expectedOut: "{\n \"key1\": \"value1\",\n \"key2\": \"C:\\\\ProgramFiles\",\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}",
147+
},
148+
{
149+
name: "Go template format",
150+
status: statusResponse,
151+
handlers: handlerResponse,
152+
info: map[string]string{"key1": "value1", "key2": "value2"},
153+
format: "go-template",
154+
tmplStr: `NetworkReady: {{ (index .status.conditions 0).status }}`,
155+
expectedOut: "NetworkReady: false",
156+
},
157+
}
158+
159+
// Run tests
160+
for _, tc := range testCases {
161+
t.Run(tc.name, func(t *testing.T) {
162+
captureOutput := func(f func() error) (string, error) {
163+
var err error
164+
old := os.Stdout
165+
166+
r, w, _ := os.Pipe()
167+
os.Stdout = w
168+
defer func() {
169+
os.Stdout = old
170+
}()
171+
172+
err = f()
173+
if err != nil {
174+
return "", err
175+
}
176+
177+
err = w.Close()
178+
if err != nil {
179+
return "", err
180+
}
181+
182+
out, err := io.ReadAll(r)
183+
return strings.TrimRight(string(out), "\n"), err
184+
}
185+
186+
outStr, err := captureOutput(func() error {
187+
err := outputStatusInfo(tc.status, tc.handlers, tc.info, tc.format, tc.tmplStr)
188+
if err != nil {
189+
t.Errorf("Unexpected error: %v", err)
190+
}
191+
return nil
192+
})
67193

194+
if err != nil {
195+
Expect(err).To(BeNil())
196+
}
197+
198+
if outStr != tc.expectedOut {
199+
t.Errorf("Expected output:\n%s\nGot:\n%s", tc.expectedOut, outStr)
200+
}
68201
})
69202
}
70203
}

0 commit comments

Comments
 (0)