Skip to content

Commit a9d9845

Browse files
authored
Merge pull request #1486 from saschagrunert/invalid-json
Fixes invalid JSON in crictl info
2 parents 09b74de + 0d18e2d commit a9d9845

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]any{}
293+
294+
if status != "" {
295+
var statusVal map[string]any
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 []*any
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]any
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)