sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/yamlprocessor/simple_processor_test.go (about) 1 /* 2 Copyright 2020 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 yamlprocessor 18 19 import ( 20 "testing" 21 22 . "github.com/onsi/gomega" 23 24 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" 25 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" 26 ) 27 28 func TestSimpleProcessor_GetTemplateName(t *testing.T) { 29 g := NewWithT(t) 30 p := NewSimpleProcessor() 31 g.Expect(p.GetTemplateName("some-version", "some-flavor")).To(Equal("cluster-template-some-flavor.yaml")) 32 g.Expect(p.GetTemplateName("", "")).To(Equal("cluster-template.yaml")) 33 } 34 35 func TestSimpleProcessor_GetVariables(t *testing.T) { 36 type args struct { 37 data string 38 } 39 tests := []struct { 40 name string 41 args args 42 want []string 43 wantErr bool 44 }{ 45 { 46 name: "variable with different spacing around the name", 47 args: args{ 48 data: "yaml with ${A} ${ B} ${ C} ${ D }", 49 }, 50 want: []string{"A", "B", "C", "D"}, 51 }, 52 { 53 name: "variables used in many places are grouped", 54 args: args{ 55 data: "yaml with ${A } ${A} ${A}", 56 }, 57 want: []string{"A"}, 58 }, 59 { 60 name: "variables in multiline texts are processed", 61 args: args{ 62 data: "yaml with ${A}\n${B}\n${C}", 63 }, 64 want: []string{"A", "B", "C"}, 65 }, 66 { 67 name: "variables are sorted", 68 args: args{ 69 data: "yaml with ${C}\n${B}\n${A}", 70 }, 71 want: []string{"A", "B", "C"}, 72 }, 73 { 74 name: "returns error for variables with regex metacharacters", 75 args: args{ 76 data: "yaml with ${BA$R}\n${FOO}", 77 }, 78 wantErr: true, 79 }, 80 { 81 name: "variables with envsubst functions are properly parsed", 82 args: args{ 83 data: "yaml with ${C:=default}\n${B}\n${A=foobar}", 84 }, 85 want: []string{"A", "B", "C"}, 86 }, 87 } 88 89 for _, tt := range tests { 90 t.Run(tt.name, func(t *testing.T) { 91 g := NewWithT(t) 92 p := NewSimpleProcessor() 93 actual, err := p.GetVariables([]byte(tt.args.data)) 94 if tt.wantErr { 95 g.Expect(err).To(HaveOccurred()) 96 return 97 } 98 g.Expect(err).ToNot(HaveOccurred()) 99 g.Expect(actual).To(Equal(tt.want)) 100 }) 101 } 102 } 103 104 func TestSimpleProcessor_GetVariablesMap(t *testing.T) { 105 type args struct { 106 data string 107 } 108 def := "default" 109 aVar := "${A}" 110 foobar := "foobar" 111 quotes := `""` 112 tests := []struct { 113 name string 114 args args 115 want map[string]*string 116 wantErr bool 117 }{ 118 { 119 name: "variable with different spacing around the name", 120 args: args{ 121 data: "yaml with ${A} ${ B} ${ C} ${ D }", 122 }, 123 want: map[string]*string{"A": nil, "B": nil, "C": nil, "D": nil}, 124 }, 125 { 126 name: "variables used in many places are grouped", 127 args: args{ 128 data: "yaml with ${A } ${A} ${A}", 129 }, 130 want: map[string]*string{"A": nil}, 131 }, 132 { 133 name: "variables in multiline texts are processed", 134 args: args{ 135 data: "yaml with ${A}\n${B}\n${C}", 136 }, 137 want: map[string]*string{"A": nil, "B": nil, "C": nil}, 138 }, 139 { 140 name: "returns error for variables with regex metacharacters", 141 args: args{ 142 data: "yaml with ${BA$R}\n${FOO}", 143 }, 144 wantErr: true, 145 }, 146 { 147 name: "variables with envsubst functions are properly parsed", 148 args: args{ 149 data: `yaml with ${C:=default}\n${B}\n${A=foobar}\n${E=""}\n${D:=${A}}`, 150 }, 151 want: map[string]*string{"A": &foobar, "B": nil, "C": &def, "D": &aVar, "E": "es}, 152 }, 153 } 154 155 for _, tt := range tests { 156 t.Run(tt.name, func(t *testing.T) { 157 g := NewWithT(t) 158 p := NewSimpleProcessor() 159 actual, err := p.GetVariableMap([]byte(tt.args.data)) 160 if tt.wantErr { 161 g.Expect(err).To(HaveOccurred()) 162 return 163 } 164 g.Expect(err).ToNot(HaveOccurred()) 165 g.Expect(actual).To(Equal(tt.want)) 166 }) 167 } 168 } 169 170 func TestSimpleProcessor_Process(t *testing.T) { 171 type args struct { 172 yaml []byte 173 configVariablesClient config.VariablesClient 174 } 175 tests := []struct { 176 name string 177 args args 178 want []byte 179 wantErr bool 180 missingVariables []string 181 }{ 182 { 183 name: "replaces legacy variables names (with spaces)", 184 args: args{ 185 yaml: []byte("foo ${ BAR }, ${BAR }, ${ BAR}"), 186 configVariablesClient: test.NewFakeVariableClient(). 187 WithVar("BAR", "bar"), 188 }, 189 want: []byte("foo bar, bar, bar"), //nolint:dupword 190 wantErr: false, 191 }, 192 { 193 name: "does not escape slashes used for windows named pipes", 194 args: args{ 195 yaml: []byte(`\\ foo ${ BAR }, ${BAR }, ${ BAR}`), 196 configVariablesClient: test.NewFakeVariableClient(). 197 WithVar("BAR", "bar"), 198 }, 199 want: []byte(`\\ foo bar, bar, bar`), //nolint:dupword 200 wantErr: false, 201 }, 202 { 203 name: "replaces variables when variable value contains regex metacharacters", 204 args: args{ 205 yaml: []byte("foo ${BAR}"), 206 configVariablesClient: test.NewFakeVariableClient(). 207 WithVar("BAR", "ba$r"), 208 }, 209 want: []byte("foo ba$r"), 210 wantErr: false, 211 }, 212 { 213 name: "uses default values if variable doesn't exist in variables client", 214 args: args{ 215 yaml: []byte("foo ${BAR=default_bar} ${BAZ:=default_baz} ${CAR=default_car} ${CAZ:-default_caz} ${DAR=default_dar}"), 216 configVariablesClient: test.NewFakeVariableClient(). 217 // CAZ,DAR is set but has no value 218 WithVar("BAR", "ba$r").WithVar("CAZ", "").WithVar("DAR", ""), 219 }, 220 want: []byte("foo ba$r default_baz default_car default_caz default_dar"), 221 wantErr: false, 222 }, 223 { 224 name: "uses default variables if main variable is doesn't exist", 225 args: args{ 226 yaml: []byte("foo ${BAR=default_bar} ${BAZ:=prefix${DEF}-suffix} ${CAZ=${DEF}}"), 227 configVariablesClient: test.NewFakeVariableClient(). 228 WithVar("BAR", "ba$r").WithVar("DEF", "football"), 229 }, 230 want: []byte("foo ba$r prefixfootball-suffix football"), 231 wantErr: false, 232 }, 233 { 234 name: "returns error with missing template variables listed (for better ux)", 235 args: args{ 236 yaml: []byte("foo ${ BAR} ${BAZ} ${CAR}"), 237 configVariablesClient: test.NewFakeVariableClient(). 238 WithVar("CAR", "car"), 239 }, 240 want: nil, 241 wantErr: true, 242 missingVariables: []string{"BAR", "BAZ"}, 243 }, 244 { 245 name: "returns error when variable name contains regex metacharacters", 246 args: args{ 247 yaml: []byte("foo ${BA$R} ${BA_R}"), 248 configVariablesClient: test.NewFakeVariableClient(). 249 WithVar("BA$R", "bar").WithVar("BA_R", "ba_r"), 250 }, 251 want: []byte("foo bar ba_r"), 252 wantErr: true, 253 }, 254 } 255 256 for _, tt := range tests { 257 t.Run(tt.name, func(t *testing.T) { 258 g := NewWithT(t) 259 p := NewSimpleProcessor() 260 261 got, err := p.Process(tt.args.yaml, tt.args.configVariablesClient.Get) 262 if tt.wantErr { 263 g.Expect(err).To(HaveOccurred()) 264 if len(tt.missingVariables) != 0 { 265 e, ok := err.(*errMissingVariables) 266 g.Expect(ok).To(BeTrue()) 267 g.Expect(e.Missing).To(ConsistOf(tt.missingVariables)) 268 } 269 // we want to ensure that we keep returning the original yaml 270 // as per the intended behavior of Process 271 g.Expect(got).To(Equal(tt.args.yaml)) 272 return 273 } 274 g.Expect(err).ToNot(HaveOccurred()) 275 276 g.Expect(got).To(Equal(tt.want)) 277 }) 278 } 279 }