...

Source file src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go

Documentation: k8s.io/kubectl/pkg/cmd/get

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package get
    18  
    19  import (
    20  	"bytes"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/cli-runtime/pkg/printers"
    29  	"k8s.io/kubectl/pkg/scheme"
    30  )
    31  
    32  // UniversalDecoder call must specify parameter versions; otherwise it will decode to internal versions.
    33  var decoder = scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...)
    34  
    35  func TestMassageJSONPath(t *testing.T) {
    36  	tests := []struct {
    37  		input          string
    38  		expectedOutput string
    39  		expectErr      bool
    40  	}{
    41  		{input: "foo.bar", expectedOutput: "{.foo.bar}"},
    42  		{input: "{foo.bar}", expectedOutput: "{.foo.bar}"},
    43  		{input: ".foo.bar", expectedOutput: "{.foo.bar}"},
    44  		{input: "{.foo.bar}", expectedOutput: "{.foo.bar}"},
    45  		{input: "", expectedOutput: ""},
    46  		{input: "{foo.bar", expectErr: true},
    47  		{input: "foo.bar}", expectErr: true},
    48  		{input: "{foo.bar}}", expectErr: true},
    49  		{input: "{{foo.bar}", expectErr: true},
    50  	}
    51  	for _, test := range tests {
    52  		t.Run(test.input, func(t *testing.T) {
    53  			output, err := RelaxedJSONPathExpression(test.input)
    54  			if err != nil && !test.expectErr {
    55  				t.Errorf("unexpected error: %v", err)
    56  				return
    57  			}
    58  			if test.expectErr {
    59  				if err == nil {
    60  					t.Error("unexpected non-error")
    61  				}
    62  				return
    63  			}
    64  			if output != test.expectedOutput {
    65  				t.Errorf("input: %s, expected: %s, saw: %s", test.input, test.expectedOutput, output)
    66  			}
    67  		})
    68  	}
    69  }
    70  
    71  func TestNewColumnPrinterFromSpec(t *testing.T) {
    72  	tests := []struct {
    73  		spec            string
    74  		expectedColumns []Column
    75  		expectErr       bool
    76  		name            string
    77  		noHeaders       bool
    78  	}{
    79  		{
    80  			spec:      "",
    81  			expectErr: true,
    82  			name:      "empty",
    83  		},
    84  		{
    85  			spec:      "invalid",
    86  			expectErr: true,
    87  			name:      "invalid1",
    88  		},
    89  		{
    90  			spec:      "invalid=foobar",
    91  			expectErr: true,
    92  			name:      "invalid2",
    93  		},
    94  		{
    95  			spec:      "invalid,foobar:blah",
    96  			expectErr: true,
    97  			name:      "invalid3",
    98  		},
    99  		{
   100  			spec: "NAME:metadata.name,API_VERSION:apiVersion",
   101  			name: "ok",
   102  			expectedColumns: []Column{
   103  				{
   104  					Header:    "NAME",
   105  					FieldSpec: "{.metadata.name}",
   106  				},
   107  				{
   108  					Header:    "API_VERSION",
   109  					FieldSpec: "{.apiVersion}",
   110  				},
   111  			},
   112  		},
   113  		{
   114  			spec:      "API_VERSION:apiVersion",
   115  			name:      "no-headers",
   116  			noHeaders: true,
   117  		},
   118  	}
   119  	for _, test := range tests {
   120  		t.Run(test.name, func(t *testing.T) {
   121  			printer, err := NewCustomColumnsPrinterFromSpec(test.spec, decoder, test.noHeaders)
   122  			if test.expectErr {
   123  				if err == nil {
   124  					t.Errorf("[%s] unexpected non-error", test.name)
   125  				}
   126  				return
   127  			}
   128  			if !test.expectErr && err != nil {
   129  				t.Errorf("[%s] unexpected error: %v", test.name, err)
   130  				return
   131  			}
   132  			if test.noHeaders {
   133  				buffer := &bytes.Buffer{}
   134  
   135  				printer.PrintObj(&corev1.Pod{}, buffer)
   136  				if err != nil {
   137  					t.Fatalf("An error occurred printing Pod: %#v", err)
   138  				}
   139  
   140  				if contains(strings.Fields(buffer.String()), "API_VERSION") {
   141  					t.Errorf("unexpected header API_VERSION")
   142  				}
   143  
   144  			} else if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
   145  				t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
   146  			}
   147  		})
   148  	}
   149  }
   150  
   151  func contains(arr []string, s string) bool {
   152  	for i := range arr {
   153  		if arr[i] == s {
   154  			return true
   155  		}
   156  	}
   157  	return false
   158  }
   159  
   160  const exampleTemplateOne = `NAME               API_VERSION
   161  {metadata.name}    {apiVersion}`
   162  
   163  const exampleTemplateTwo = `NAME               		API_VERSION
   164  							{metadata.name}    {apiVersion}`
   165  
   166  func TestNewColumnPrinterFromTemplate(t *testing.T) {
   167  	tests := []struct {
   168  		spec            string
   169  		expectedColumns []Column
   170  		expectErr       bool
   171  		name            string
   172  	}{
   173  		{
   174  			spec:      "",
   175  			expectErr: true,
   176  			name:      "empty",
   177  		},
   178  		{
   179  			spec:      "invalid",
   180  			expectErr: true,
   181  			name:      "invalid1",
   182  		},
   183  		{
   184  			spec:      "invalid=foobar",
   185  			expectErr: true,
   186  			name:      "invalid2",
   187  		},
   188  		{
   189  			spec:      "invalid,foobar:blah",
   190  			expectErr: true,
   191  			name:      "invalid3",
   192  		},
   193  		{
   194  			spec: exampleTemplateOne,
   195  			name: "ok",
   196  			expectedColumns: []Column{
   197  				{
   198  					Header:    "NAME",
   199  					FieldSpec: "{.metadata.name}",
   200  				},
   201  				{
   202  					Header:    "API_VERSION",
   203  					FieldSpec: "{.apiVersion}",
   204  				},
   205  			},
   206  		},
   207  		{
   208  			spec: exampleTemplateTwo,
   209  			name: "ok-2",
   210  			expectedColumns: []Column{
   211  				{
   212  					Header:    "NAME",
   213  					FieldSpec: "{.metadata.name}",
   214  				},
   215  				{
   216  					Header:    "API_VERSION",
   217  					FieldSpec: "{.apiVersion}",
   218  				},
   219  			},
   220  		},
   221  	}
   222  	for _, test := range tests {
   223  		t.Run(test.name, func(t *testing.T) {
   224  			reader := bytes.NewBufferString(test.spec)
   225  			printer, err := NewCustomColumnsPrinterFromTemplate(reader, decoder)
   226  			if test.expectErr {
   227  				if err == nil {
   228  					t.Errorf("[%s] unexpected non-error", test.name)
   229  				}
   230  				return
   231  			}
   232  			if !test.expectErr && err != nil {
   233  				t.Errorf("[%s] unexpected error: %v", test.name, err)
   234  				return
   235  			}
   236  
   237  			if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
   238  				t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
   239  			}
   240  		})
   241  	}
   242  }
   243  
   244  func TestColumnPrint(t *testing.T) {
   245  	tests := []struct {
   246  		columns        []Column
   247  		obj            runtime.Object
   248  		expectedOutput string
   249  	}{
   250  		{
   251  			columns: []Column{
   252  				{
   253  					Header:    "NAME",
   254  					FieldSpec: "{.metadata.name}",
   255  				},
   256  			},
   257  			obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
   258  			expectedOutput: `NAME
   259  foo
   260  `,
   261  		},
   262  		{
   263  			columns: []Column{
   264  				{
   265  					Header:    "NAME",
   266  					FieldSpec: "{.metadata.name}",
   267  				},
   268  			},
   269  			obj: &corev1.PodList{
   270  				Items: []corev1.Pod{
   271  					{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
   272  					{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
   273  				},
   274  			},
   275  			expectedOutput: `NAME
   276  foo
   277  bar
   278  `,
   279  		},
   280  		{
   281  			columns: []Column{
   282  				{
   283  					Header:    "NAME",
   284  					FieldSpec: "{.metadata.name}",
   285  				},
   286  				{
   287  					Header:    "API_VERSION",
   288  					FieldSpec: "{.apiVersion}",
   289  				},
   290  			},
   291  			obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
   292  			expectedOutput: `NAME   API_VERSION
   293  foo    baz
   294  `,
   295  		},
   296  		{
   297  			columns: []Column{
   298  				{
   299  					Header:    "NAME",
   300  					FieldSpec: "{.metadata.name}",
   301  				},
   302  				{
   303  					Header:    "API_VERSION",
   304  					FieldSpec: "{.apiVersion}",
   305  				},
   306  				{
   307  					Header:    "NOT_FOUND",
   308  					FieldSpec: "{.notFound}",
   309  				},
   310  			},
   311  			obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
   312  			expectedOutput: `NAME   API_VERSION   NOT_FOUND
   313  foo    baz           <none>
   314  `,
   315  		},
   316  		{
   317  			columns: []Column{
   318  				{
   319  					Header:    "NAME",
   320  					FieldSpec: "{.metadata.name}",
   321  				},
   322  			},
   323  			obj: &corev1.PodList{
   324  				Items: []corev1.Pod{
   325  					{ObjectMeta: metav1.ObjectMeta{Name: "\x1b \r"}},
   326  				},
   327  			},
   328  			expectedOutput: `NAME
   329  ^[ \r
   330  `,
   331  		},
   332  	}
   333  
   334  	for _, test := range tests {
   335  		t.Run(test.expectedOutput, func(t *testing.T) {
   336  			printer := &CustomColumnsPrinter{
   337  				Columns: test.columns,
   338  				Decoder: decoder,
   339  			}
   340  			buffer := &bytes.Buffer{}
   341  			if err := printer.PrintObj(test.obj, buffer); err != nil {
   342  				t.Errorf("unexpected error: %v", err)
   343  			}
   344  			if buffer.String() != test.expectedOutput {
   345  				t.Errorf("\nexpected:\n'%s'\nsaw\n'%s'\n", test.expectedOutput, buffer.String())
   346  			}
   347  		})
   348  	}
   349  }
   350  
   351  // this mimics how resource/get.go calls the customcolumn printer
   352  func TestIndividualPrintObjOnExistingTabWriter(t *testing.T) {
   353  	columns := []Column{
   354  		{
   355  			Header:    "NAME",
   356  			FieldSpec: "{.metadata.name}",
   357  		},
   358  		{
   359  			Header:    "LONG COLUMN NAME", // name is longer than all values of label1
   360  			FieldSpec: "{.metadata.labels.label1}",
   361  		},
   362  		{
   363  			Header:    "LABEL 2",
   364  			FieldSpec: "{.metadata.labels.label2}",
   365  		},
   366  	}
   367  	objects := []*corev1.Pod{
   368  		{ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"label1": "foo", "label2": "foo"}}},
   369  		{ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{"label1": "bar", "label2": "bar"}}},
   370  	}
   371  	expectedOutput := `NAME   LONG COLUMN NAME   LABEL 2
   372  foo    foo                foo
   373  bar    bar                bar
   374  `
   375  
   376  	buffer := &bytes.Buffer{}
   377  	tabWriter := printers.GetNewTabWriter(buffer)
   378  	printer := &CustomColumnsPrinter{
   379  		Columns: columns,
   380  		Decoder: decoder,
   381  	}
   382  	for _, obj := range objects {
   383  		if err := printer.PrintObj(obj, tabWriter); err != nil {
   384  			t.Errorf("unexpected error: %v", err)
   385  		}
   386  	}
   387  	tabWriter.Flush()
   388  	if buffer.String() != expectedOutput {
   389  		t.Errorf("\nexpected:\n'%s'\nsaw\n'%s'\n", expectedOutput, buffer.String())
   390  	}
   391  }
   392  
   393  func TestSliceColumnPrint(t *testing.T) {
   394  	pod := &corev1.Pod{
   395  		ObjectMeta: metav1.ObjectMeta{
   396  			Name:      "fake-name",
   397  			Namespace: "fake-namespace",
   398  		},
   399  		Spec: corev1.PodSpec{
   400  			Containers: []corev1.Container{
   401  				{
   402  					Name: "fake0",
   403  				},
   404  				{
   405  					Name: "fake1",
   406  				},
   407  				{
   408  					Name: "fake2",
   409  				},
   410  				{
   411  					Name: "fake3",
   412  				},
   413  			},
   414  		},
   415  	}
   416  
   417  	tests := []struct {
   418  		name           string
   419  		spec           string
   420  		expectedOutput string
   421  		expectErr      bool
   422  	}{
   423  		{
   424  			name: "containers[0]",
   425  			spec: "CONTAINER:.spec.containers[0].name",
   426  			expectedOutput: `CONTAINER
   427  fake0
   428  `,
   429  			expectErr: false,
   430  		},
   431  		{
   432  			name: "containers[3]",
   433  			spec: "CONTAINER:.spec.containers[3].name",
   434  			expectedOutput: `CONTAINER
   435  fake3
   436  `,
   437  			expectErr: false,
   438  		},
   439  		{
   440  			name:           "containers[5], illegal expression because it is out of bounds",
   441  			spec:           "CONTAINER:.spec.containers[5].name",
   442  			expectedOutput: "",
   443  			expectErr:      true,
   444  		},
   445  		{
   446  			name: "containers[-1], it equals containers[3]",
   447  			spec: "CONTAINER:.spec.containers[-1].name",
   448  			expectedOutput: `CONTAINER
   449  fake3
   450  `,
   451  			expectErr: false,
   452  		},
   453  		{
   454  			name: "containers[-2], it equals containers[2]",
   455  			spec: "CONTAINER:.spec.containers[-2].name",
   456  			expectedOutput: `CONTAINER
   457  fake2
   458  `,
   459  			expectErr: false,
   460  		},
   461  		{
   462  			name: "containers[-4], it equals containers[0]",
   463  			spec: "CONTAINER:.spec.containers[-4].name",
   464  			expectedOutput: `CONTAINER
   465  fake0
   466  `,
   467  			expectErr: false,
   468  		},
   469  		{
   470  			name:           "containers[-5], illegal expression because it is out of bounds",
   471  			spec:           "CONTAINER:.spec.containers[-5].name",
   472  			expectedOutput: "",
   473  			expectErr:      true,
   474  		},
   475  		{
   476  			name: "containers[0:0], it equals empty set",
   477  			spec: "CONTAINER:.spec.containers[0:0].name",
   478  			expectedOutput: `CONTAINER
   479  <none>
   480  `,
   481  			expectErr: false,
   482  		},
   483  		{
   484  			name: "containers[0:3]",
   485  			spec: "CONTAINER:.spec.containers[0:3].name",
   486  			expectedOutput: `CONTAINER
   487  fake0,fake1,fake2
   488  `,
   489  			expectErr: false,
   490  		},
   491  		{
   492  			name: "containers[1:]",
   493  			spec: "CONTAINER:.spec.containers[1:].name",
   494  			expectedOutput: `CONTAINER
   495  fake1,fake2,fake3
   496  `,
   497  			expectErr: false,
   498  		},
   499  		{
   500  			name:           "containers[3:1], illegal expression because start index is greater than end index",
   501  			spec:           "CONTAINER:.spec.containers[3:1].name",
   502  			expectedOutput: "",
   503  			expectErr:      true,
   504  		},
   505  
   506  		{
   507  			name: "containers[0:-1], it equals containers[0:3]",
   508  			spec: "CONTAINER:.spec.containers[0:-1].name",
   509  			expectedOutput: `CONTAINER
   510  fake0,fake1,fake2
   511  `,
   512  			expectErr: false,
   513  		},
   514  		{
   515  			name: "containers[-1:], it equals containers[3:]",
   516  			spec: "CONTAINER:.spec.containers[-1:].name",
   517  			expectedOutput: `CONTAINER
   518  fake3
   519  `,
   520  			expectErr: false,
   521  		},
   522  		{
   523  			name: "containers[-4:], it equals containers[0:]",
   524  			spec: "CONTAINER:.spec.containers[-4:].name",
   525  			expectedOutput: `CONTAINER
   526  fake0,fake1,fake2,fake3
   527  `,
   528  			expectErr: false,
   529  		},
   530  
   531  		{
   532  			name: "containers[-3:-1], it equasl containers[1:3]",
   533  			spec: "CONTAINER:.spec.containers[-3:-1].name",
   534  			expectedOutput: `CONTAINER
   535  fake1,fake2
   536  `,
   537  			expectErr: false,
   538  		},
   539  		{
   540  			name:           "containers[-2:-3], it equals containers[2:1], illegal expression because start index is greater than end index",
   541  			spec:           "CONTAINER:.spec.containers[-2:-3].name",
   542  			expectedOutput: "",
   543  			expectErr:      true,
   544  		},
   545  		{
   546  			name: "containers[4:4], it equals empty set",
   547  			spec: "CONTAINER:.spec.containers[4:4].name",
   548  			expectedOutput: `CONTAINER
   549  <none>
   550  `,
   551  			expectErr: false,
   552  		},
   553  		{
   554  			name: "containers[-5:-5], it equals empty set",
   555  			spec: "CONTAINER:.spec.containers[-5:-5].name",
   556  			expectedOutput: `CONTAINER
   557  <none>
   558  `,
   559  			expectErr: false,
   560  		},
   561  	}
   562  
   563  	for _, test := range tests {
   564  		t.Run(test.name, func(t *testing.T) {
   565  			printer, err := NewCustomColumnsPrinterFromSpec(test.spec, decoder, false)
   566  			if err != nil {
   567  				t.Errorf("test %s has unexpected error: %v", test.name, err)
   568  			}
   569  
   570  			buffer := &bytes.Buffer{}
   571  			err = printer.PrintObj(pod, buffer)
   572  			if test.expectErr {
   573  				if err == nil {
   574  					t.Errorf("test %s has unexpected error: %v", test.name, err)
   575  
   576  				}
   577  			} else {
   578  				if err != nil {
   579  					t.Errorf("test %s has unexpected error: %v", test.name, err)
   580  				} else if buffer.String() != test.expectedOutput {
   581  					t.Errorf("test %s has unexpected output:\nexpected: %s\nsaw: %s", test.name, test.expectedOutput, buffer.String())
   582  				}
   583  			}
   584  		})
   585  	}
   586  }
   587  

View as plain text