...

Source file src/sigs.k8s.io/kustomize/kyaml/yaml/match_test.go

Documentation: sigs.k8s.io/kustomize/kyaml/yaml

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package yaml
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	yaml "sigs.k8s.io/yaml/goyaml.v3"
    13  )
    14  
    15  func TestPathMatcher_Filter(t *testing.T) {
    16  	node := MustParse(`apiVersion: apps/v1
    17  kind: Deployment
    18  metadata:
    19    name: nginx-deployment
    20    labels:
    21      app: nginx
    22  spec:
    23    replicas: 3
    24    selector:
    25      matchLabels:
    26        app: nginx
    27    template:
    28      metadata:
    29        labels:
    30          app: nginx
    31      spec:
    32        containers:
    33        - name: nginx
    34          image: nginx:1.7.9
    35          args: [-c, conf.yaml]
    36          ports:
    37          - containerPort: 80
    38        - name: sidecar
    39          image: sidecar:1.0.0
    40          ports:
    41          - containerPort: 8081
    42          - containerPort: 9090
    43  `)
    44  
    45  	updates := []struct {
    46  		path  []string
    47  		value string
    48  	}{
    49  		{[]string{
    50  			"spec", "template", "spec", "containers", "[name=.*]"},
    51  			"- name: nginx\n  image: nginx:1.7.9\n  args: [-c, conf.yaml]\n  ports:\n  - containerPort: 80\n" +
    52  				"- name: sidecar\n  image: sidecar:1.0.0\n  ports:\n  - containerPort: 8081\n  - containerPort: 9090\n"},
    53  		{[]string{
    54  			"spec", "template", "spec", "containers", "[name=.*]", "image"},
    55  			"- nginx:1.7.9\n- sidecar:1.0.0\n"},
    56  		{[]string{
    57  			"spec", "template", "spec", "containers", "[name=n.*]", "image"},
    58  			"- nginx:1.7.9\n"},
    59  		{[]string{
    60  			"spec", "template", "spec", "containers", "[name=s.*]", "image"},
    61  			"- sidecar:1.0.0\n"},
    62  		{[]string{
    63  			"spec", "template", "spec", "containers", "[name=.*x]", "image"},
    64  			"- nginx:1.7.9\n"},
    65  		{[]string{
    66  			"spec", "template", "spec", "containers", "[name=.*]", "ports"},
    67  			"- - containerPort: 80\n- - containerPort: 8081\n  - containerPort: 9090\n"},
    68  		{[]string{
    69  			"spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=8.*]"},
    70  			"- containerPort: 80\n- containerPort: 8081\n"},
    71  		{[]string{
    72  			"spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=.*1]"},
    73  			"- containerPort: 8081\n"},
    74  		{[]string{
    75  			"spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=9.*]"},
    76  			"- containerPort: 9090\n"},
    77  		{[]string{
    78  			"spec", "template", "spec", "containers", "[name=s.*]", "ports", "[containerPort=8.*]"},
    79  			"- containerPort: 8081\n"},
    80  		{[]string{
    81  			"spec", "template", "spec", "containers", "[name=s.*]", "ports", "[containerPort=.*2]"},
    82  			""},
    83  		{[]string{
    84  			"spec", "template", "spec", "containers", "*", "image"},
    85  			"- nginx:1.7.9\n- sidecar:1.0.0\n"},
    86  		{[]string{
    87  			"spec", "template", "spec", "containers", "*", "ports", "*"},
    88  			"- containerPort: 80\n- containerPort: 8081\n- containerPort: 9090\n"},
    89  		{[]string{
    90  			"spec", "template", "spec", "containers", "[name=.*]", "args", "1"},
    91  			"- conf.yaml\n"},
    92  	}
    93  	for i, u := range updates {
    94  		result, err := node.Pipe(&PathMatcher{Path: u.path})
    95  		if !assert.NoError(t, err) {
    96  			return
    97  		}
    98  		assert.Equal(t, u.value, result.MustString(), fmt.Sprintf("%d", i))
    99  	}
   100  }
   101  
   102  func TestPathMatcher_Filter_Create(t *testing.T) {
   103  	testCases := map[string]struct {
   104  		path                    []string
   105  		matches                 []string
   106  		modifiedNodeMustContain string
   107  		create                  yaml.Kind
   108  		expectErr               string
   109  	}{
   110  		"create non-primitive sequence item that does not exist": {
   111  			path: []string{"spec", "template", "spec", "containers", "[name=please-create-me]"},
   112  			matches: []string{
   113  				"name: please-create-me\n",
   114  			},
   115  			modifiedNodeMustContain: "- name: please-create-me",
   116  			create:                  yaml.MappingNode,
   117  		},
   118  		"create non-primitive item in empty sequence by index": {
   119  			path:                    []string{"spec", "template", "spec", "containers", "[name=nginx]", "envFrom", "0"},
   120  			matches:                 []string{"{}\n"},
   121  			modifiedNodeMustContain: "envFrom:\n        - {}\n",
   122  			create:                  yaml.MappingNode,
   123  		},
   124  		"create primitive item in empty sequence by index": {
   125  			path:                    []string{"spec", "template", "spec", "containers", "[name=sidecar]", "args", "0"},
   126  			matches:                 []string{"\n"},
   127  			modifiedNodeMustContain: "args:\n        -\n",
   128  			create:                  yaml.ScalarNode,
   129  		},
   130  		"append primitive item to sequence by index": {
   131  			path:                    []string{"spec", "template", "spec", "containers", "[name=nginx]", "args", "2"},
   132  			matches:                 []string{"\n"},
   133  			create:                  yaml.ScalarNode,
   134  			modifiedNodeMustContain: "args: [-c, conf.yaml, '']",
   135  		},
   136  		"append non-primitive item to sequence by index": {
   137  			path:                    []string{"spec", "template", "spec", "containers", "[name=nginx]", "ports", "1"},
   138  			matches:                 []string{"{}\n"},
   139  			modifiedNodeMustContain: "ports: [{containerPort: 80}, {}]",
   140  			create:                  yaml.MappingNode,
   141  		},
   142  		"appending non-primitive element in middle of sequence": {
   143  			path:                    []string{"spec", "template", "spec", "containers", "2", "imagePullPolicy"},
   144  			matches:                 []string{"\n"},
   145  			create:                  yaml.ScalarNode,
   146  			modifiedNodeMustContain: "\n      - imagePullPolicy:\n",
   147  		},
   148  		"fail to create non-primitive item by non-zero index in created sequence": {
   149  			path:      []string{"spec", "template", "spec", "containers", "[name=nginx]", "envFrom", "1"},
   150  			matches:   []string{},
   151  			create:    yaml.MappingNode,
   152  			expectErr: "index 1 specified but only 0 elements found",
   153  		},
   154  		"fail to create primitive item by non-zero index in created sequence": {
   155  			path:      []string{"spec", "template", "spec", "containers", "[name=sidecar]", "args", "1"},
   156  			matches:   []string{},
   157  			create:    yaml.ScalarNode,
   158  			expectErr: "index 1 specified but only 0 elements found",
   159  		},
   160  		"fail to create non-primitive item by distant index in existing sequence": {
   161  			path:      []string{"spec", "template", "spec", "containers", "3"},
   162  			matches:   []string{},
   163  			create:    yaml.MappingNode,
   164  			expectErr: "index 3 specified but only 2 elements found",
   165  		},
   166  		"fail to create primitive item by distant index in existing sequence": {
   167  			path:      []string{"spec", "template", "spec", "containers", "[name=nginx]", "args", "3"},
   168  			matches:   []string{},
   169  			create:    yaml.ScalarNode,
   170  			expectErr: "index 3 specified but only 2 elements found",
   171  		},
   172  		"create primitive sequence item that does not exist": {
   173  			path: []string{"metadata", "finalizers", "[=create-me]"},
   174  			matches: []string{
   175  				"create-me\n",
   176  			},
   177  			modifiedNodeMustContain: "finalizers:\n  - create-me\n",
   178  			create:                  yaml.ScalarNode,
   179  		},
   180  		"create series of maps that do not exist": {
   181  			path: []string{"spec", "selector", "matchLabels", "does-not-exist"},
   182  			matches: []string{
   183  				"{}\n",
   184  			},
   185  			modifiedNodeMustContain: "selector:\n    matchLabels:\n      app: nginx\n      does-not-exist: {}\n",
   186  			create:                  yaml.MappingNode,
   187  		},
   188  		"create scalar below series of maps and sequences that do not exist": {
   189  			path: []string{"spec", "template", "spec", "containers", "[name=please-create-me]", "env", "[key=please-create-me]", "value"},
   190  			matches: []string{
   191  				"\n",
   192  			},
   193  			modifiedNodeMustContain: "- name: please-create-me\n        env:\n        - key: please-create-me\n          value:\n",
   194  			create:                  yaml.ScalarNode,
   195  		},
   196  		"find sequence items that already exist": {
   197  			path: []string{"spec", "template", "spec", "containers", "[name=.*]"},
   198  			matches: []string{
   199  				"name: nginx\nimage: nginx:1.7.9\nargs: [-c, conf.yaml]\nports: [{containerPort: 80}]\nenv:\n- key: CONTAINER_NAME\n  value: nginx\n",
   200  				"name: sidecar\nimage: sidecar:1.0.0\nports:\n- containerPort: 8081\n- containerPort: 9090\n",
   201  			},
   202  			create: yaml.MappingNode,
   203  		},
   204  		"find and create sequence below wildcard that exists on some sequence items": {
   205  			path: []string{"spec", "template", "spec", "containers", "[name=.*]", "env"},
   206  			matches: []string{
   207  				"- key: CONTAINER_NAME\n  value: nginx\n",
   208  				"[]\n",
   209  			},
   210  			create: yaml.SequenceNode,
   211  		},
   212  		"find field below wildcard that exists on all sequence items": {
   213  			path: []string{"spec", "template", "spec", "containers", "[name=.*]", "ports"},
   214  			matches: []string{
   215  				"[{containerPort: 80}]\n",
   216  				"- containerPort: 8081\n- containerPort: 9090\n",
   217  			},
   218  			create: yaml.SequenceNode,
   219  		},
   220  		"find field below query that targets a specific item": {
   221  			path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "env"},
   222  			matches: []string{
   223  				"- key: CONTAINER_NAME\n  value: nginx\n",
   224  			},
   225  			create: yaml.SequenceNode,
   226  		},
   227  		"create field below query that targets any value of a field that does not exist": {
   228  			path: []string{"spec", "template", "spec", "containers", "[foo=.*]", "env"},
   229  			matches: []string{
   230  				"[]\n",
   231  			},
   232  			// This is kinda weird. The query doesn't match anything, and we can't tell that it is a
   233  			// wildcard rather than a literal, so we use the value to create the field.
   234  			modifiedNodeMustContain: "- foo: .*\n        env: []\n",
   235  			create:                  yaml.SequenceNode,
   236  		},
   237  	}
   238  	nodeStr := `apiVersion: apps/v1
   239  kind: Deployment
   240  metadata:
   241    name: nginx-deployment
   242    labels:
   243      app: nginx
   244  spec:
   245    replicas: 3
   246    selector:
   247      matchLabels:
   248        app: nginx
   249    template:
   250      metadata:
   251        labels:
   252          app: nginx
   253      spec:
   254        containers:
   255        - name: nginx
   256          image: nginx:1.7.9
   257          args: [-c, conf.yaml]
   258          ports: [{containerPort: 80}]
   259          env:
   260          - key: CONTAINER_NAME
   261            value: nginx
   262        - name: sidecar
   263          image: sidecar:1.0.0
   264          ports:
   265          - containerPort: 8081
   266          - containerPort: 9090
   267  `
   268  	for name, tc := range testCases {
   269  		t.Run(name, func(t *testing.T) {
   270  			node := MustParse(nodeStr)
   271  			result, err := node.Pipe(&PathMatcher{Path: tc.path, Create: tc.create})
   272  			if tc.expectErr != "" {
   273  				require.EqualError(t, err, tc.expectErr)
   274  				return
   275  			}
   276  			require.NoError(t, err)
   277  			matches, err := result.Elements()
   278  			require.NoError(t, err)
   279  			require.Equalf(t, len(tc.matches), len(matches), "Full sequence wrapper of result:\n%s", result.MustString())
   280  
   281  			modifiedNode := node.MustString()
   282  			for i, expected := range tc.matches {
   283  				assert.Equal(t, tc.create, matches[i].YNode().Kind)
   284  				assert.Equal(t, expected, matches[i].MustString())
   285  				assert.Contains(t, modifiedNode, tc.modifiedNodeMustContain)
   286  			}
   287  		})
   288  	}
   289  }
   290  

View as plain text