Skip to content

Commit 371daf1

Browse files
author
Takashi Kusumi
authored
Refactor template logic (#233)
* Remove unnecessary no-color template * Move template logic to function * Add unit test for template logic
1 parent 202f7e8 commit 371daf1

File tree

2 files changed

+256
-67
lines changed

2 files changed

+256
-67
lines changed

cmd/cmd.go

Lines changed: 67 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -234,74 +234,9 @@ func (o *options) sternConfig() (*stern.Config, error) {
234234
return nil, errors.New("color should be one of 'always', 'never', or 'auto'")
235235
}
236236

237-
t := o.template
238-
if t == "" {
239-
switch o.output {
240-
case "default":
241-
if color.NoColor {
242-
t = "{{.PodName}} {{.ContainerName}} {{.Message}}"
243-
if o.allNamespaces || len(o.namespaces) > 1 {
244-
t = fmt.Sprintf("{{.Namespace}} %s", t)
245-
}
246-
} else {
247-
t = "{{color .PodColor .PodName}} {{color .ContainerColor .ContainerName}} {{.Message}}"
248-
if o.allNamespaces || len(o.namespaces) > 1 {
249-
t = fmt.Sprintf("{{color .PodColor .Namespace}} %s", t)
250-
}
251-
252-
}
253-
case "raw":
254-
t = "{{.Message}}"
255-
case "json":
256-
t = "{{json .}}"
257-
case "extjson":
258-
t = "\"pod\": \"{{color .PodColor .PodName}}\", \"container\": \"{{color .ContainerColor .ContainerName}}\", \"message\": {{extjson .Message}}"
259-
if o.allNamespaces {
260-
t = fmt.Sprintf("\"namespace\": \"{{color .PodColor .Namespace}}\", %s", t)
261-
}
262-
t = fmt.Sprintf("{%s}", t)
263-
case "ppextjson":
264-
t = " \"pod\": \"{{color .PodColor .PodName}}\",\n \"container\": \"{{color .ContainerColor .ContainerName}}\",\n \"message\": {{extjson .Message}}"
265-
if o.allNamespaces {
266-
t = fmt.Sprintf(" \"namespace\": \"{{color .PodColor .Namespace}}\",\n%s", t)
267-
}
268-
t = fmt.Sprintf("{\n%s\n}", t)
269-
}
270-
t += "\n"
271-
}
272-
273-
funs := map[string]interface{}{
274-
"json": func(in interface{}) (string, error) {
275-
b, err := json.Marshal(in)
276-
if err != nil {
277-
return "", err
278-
}
279-
return string(b), nil
280-
},
281-
"parseJSON": func(text string) (map[string]interface{}, error) {
282-
obj := make(map[string]interface{})
283-
if err := json.Unmarshal([]byte(text), &obj); err != nil {
284-
return obj, err
285-
}
286-
return obj, nil
287-
},
288-
"extjson": func(in string) (string, error) {
289-
if json.Valid([]byte(in)) {
290-
return strings.TrimSuffix(in, "\n"), nil
291-
}
292-
b, err := json.Marshal(in)
293-
if err != nil {
294-
return "", err
295-
}
296-
return strings.TrimSuffix(string(b), "\n"), nil
297-
},
298-
"color": func(color color.Color, text string) string {
299-
return color.SprintFunc()(text)
300-
},
301-
}
302-
template, err := template.New("log").Funcs(funs).Parse(t)
237+
template, err := o.generateTemplate()
303238
if err != nil {
304-
return nil, errors.Wrap(err, "unable to parse template")
239+
return nil, err
305240
}
306241

307242
namespaces := []string{}
@@ -402,6 +337,71 @@ func (o *options) AddFlags(fs *pflag.FlagSet) {
402337
fs.BoolVarP(&o.version, "version", "v", o.version, "Print the version and exit.")
403338
}
404339

340+
func (o *options) generateTemplate() (*template.Template, error) {
341+
t := o.template
342+
if t == "" {
343+
switch o.output {
344+
case "default":
345+
t = "{{color .PodColor .PodName}} {{color .ContainerColor .ContainerName}} {{.Message}}"
346+
if o.allNamespaces || len(o.namespaces) > 1 {
347+
t = fmt.Sprintf("{{color .PodColor .Namespace}} %s", t)
348+
}
349+
case "raw":
350+
t = "{{.Message}}"
351+
case "json":
352+
t = "{{json .}}"
353+
case "extjson":
354+
t = "\"pod\": \"{{color .PodColor .PodName}}\", \"container\": \"{{color .ContainerColor .ContainerName}}\", \"message\": {{extjson .Message}}"
355+
if o.allNamespaces {
356+
t = fmt.Sprintf("\"namespace\": \"{{color .PodColor .Namespace}}\", %s", t)
357+
}
358+
t = fmt.Sprintf("{%s}", t)
359+
case "ppextjson":
360+
t = " \"pod\": \"{{color .PodColor .PodName}}\",\n \"container\": \"{{color .ContainerColor .ContainerName}}\",\n \"message\": {{extjson .Message}}"
361+
if o.allNamespaces {
362+
t = fmt.Sprintf(" \"namespace\": \"{{color .PodColor .Namespace}}\",\n%s", t)
363+
}
364+
t = fmt.Sprintf("{\n%s\n}", t)
365+
}
366+
t += "\n"
367+
}
368+
369+
funs := map[string]interface{}{
370+
"json": func(in interface{}) (string, error) {
371+
b, err := json.Marshal(in)
372+
if err != nil {
373+
return "", err
374+
}
375+
return string(b), nil
376+
},
377+
"parseJSON": func(text string) (map[string]interface{}, error) {
378+
obj := make(map[string]interface{})
379+
if err := json.Unmarshal([]byte(text), &obj); err != nil {
380+
return obj, err
381+
}
382+
return obj, nil
383+
},
384+
"extjson": func(in string) (string, error) {
385+
if json.Valid([]byte(in)) {
386+
return strings.TrimSuffix(in, "\n"), nil
387+
}
388+
b, err := json.Marshal(in)
389+
if err != nil {
390+
return "", err
391+
}
392+
return strings.TrimSuffix(string(b), "\n"), nil
393+
},
394+
"color": func(color color.Color, text string) string {
395+
return color.SprintFunc()(text)
396+
},
397+
}
398+
template, err := template.New("log").Funcs(funs).Parse(t)
399+
if err != nil {
400+
return nil, errors.Wrap(err, "unable to parse template")
401+
}
402+
return template, err
403+
}
404+
405405
func NewSternCmd(stream genericclioptions.IOStreams) (*cobra.Command, error) {
406406
o := NewOptions(stream)
407407

cmd/cmd_test.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"strings"
56
"testing"
67

8+
"github.com/fatih/color"
9+
"github.com/stern/stern/stern"
710
"k8s.io/cli-runtime/pkg/genericclioptions"
811
)
912

@@ -126,3 +129,189 @@ func TestOptionsValidate(t *testing.T) {
126129
})
127130
}
128131
}
132+
133+
func TestOptionsGenerateTemplate(t *testing.T) {
134+
t.Setenv("NO_COLOR", "1")
135+
streams := genericclioptions.NewTestIOStreamsDiscard()
136+
137+
tests := []struct {
138+
name string
139+
o *options
140+
message string
141+
want string
142+
wantError bool
143+
}{
144+
{
145+
"output=default",
146+
func() *options {
147+
o := NewOptions(streams)
148+
o.output = "default"
149+
150+
return o
151+
}(),
152+
"default message",
153+
"pod1 container1 default message\n",
154+
false,
155+
},
156+
{
157+
"output=default+allNamespaces",
158+
func() *options {
159+
o := NewOptions(streams)
160+
o.output = "default"
161+
o.allNamespaces = true
162+
163+
return o
164+
}(),
165+
"default message",
166+
"ns1 pod1 container1 default message\n",
167+
false,
168+
},
169+
{
170+
"output=raw",
171+
func() *options {
172+
o := NewOptions(streams)
173+
o.output = "raw"
174+
175+
return o
176+
}(),
177+
"raw message",
178+
"raw message\n",
179+
false,
180+
},
181+
{
182+
"output=json",
183+
func() *options {
184+
o := NewOptions(streams)
185+
o.output = "json"
186+
187+
return o
188+
}(),
189+
"json message",
190+
`{"message":"json message","nodeName":"node1","namespace":"ns1","podName":"pod1","containerName":"container1"}
191+
`,
192+
false,
193+
},
194+
{
195+
"output=extjson",
196+
func() *options {
197+
o := NewOptions(streams)
198+
o.output = "extjson"
199+
200+
return o
201+
}(),
202+
`{"msg":"extjson message"}`,
203+
`{"pod": "pod1", "container": "container1", "message": {"msg":"extjson message"}}
204+
`,
205+
false,
206+
},
207+
{
208+
"output=extjson+allNamespaces",
209+
func() *options {
210+
o := NewOptions(streams)
211+
o.output = "extjson"
212+
o.allNamespaces = true
213+
214+
return o
215+
}(),
216+
`{"msg":"extjson message"}`,
217+
`{"namespace": "ns1", "pod": "pod1", "container": "container1", "message": {"msg":"extjson message"}}
218+
`,
219+
false,
220+
},
221+
{
222+
"output=ppextjson",
223+
func() *options {
224+
o := NewOptions(streams)
225+
o.output = "ppextjson"
226+
227+
return o
228+
}(),
229+
`{"msg":"ppextjson message"}`,
230+
`{
231+
"pod": "pod1",
232+
"container": "container1",
233+
"message": {"msg":"ppextjson message"}
234+
}
235+
`,
236+
false,
237+
},
238+
{
239+
"output=ppextjson+allNamespaces",
240+
func() *options {
241+
o := NewOptions(streams)
242+
o.output = "ppextjson"
243+
o.allNamespaces = true
244+
245+
return o
246+
}(),
247+
`{"msg":"ppextjson message"}`,
248+
`{
249+
"namespace": "ns1",
250+
"pod": "pod1",
251+
"container": "container1",
252+
"message": {"msg":"ppextjson message"}
253+
}
254+
`,
255+
false,
256+
},
257+
{
258+
"template",
259+
func() *options {
260+
o := NewOptions(streams)
261+
o.template = "Message={{.Message}} NodeName={{.NodeName}} Namespace={{.Namespace}} PodName={{.PodName}} ContainerName={{.ContainerName}}"
262+
263+
return o
264+
}(),
265+
"template message", // no new line
266+
"Message=template message NodeName=node1 Namespace=ns1 PodName=pod1 ContainerName=container1",
267+
false,
268+
},
269+
{
270+
"invalid template",
271+
func() *options {
272+
o := NewOptions(streams)
273+
o.template = "{{invalid"
274+
275+
return o
276+
}(),
277+
"template message",
278+
"",
279+
true,
280+
},
281+
}
282+
283+
for _, tt := range tests {
284+
t.Run(tt.name, func(t *testing.T) {
285+
log := stern.Log{
286+
Message: tt.message,
287+
NodeName: "node1",
288+
Namespace: "ns1",
289+
PodName: "pod1",
290+
ContainerName: "container1",
291+
PodColor: color.New(color.FgRed),
292+
ContainerColor: color.New(color.FgBlue),
293+
}
294+
tmpl, err := tt.o.generateTemplate()
295+
296+
if tt.wantError {
297+
if err == nil {
298+
t.Errorf("expected error, but got no error")
299+
}
300+
return
301+
}
302+
if err != nil {
303+
t.Errorf("unexpected error: %v", err)
304+
return
305+
}
306+
307+
var buf bytes.Buffer
308+
if err := tmpl.Execute(&buf, log); err != nil {
309+
t.Errorf("unexpected error: %v", err)
310+
return
311+
}
312+
if want, got := tt.want, buf.String(); want != got {
313+
t.Errorf("want %v, but got %v", want, got)
314+
}
315+
})
316+
}
317+
}

0 commit comments

Comments
 (0)