istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/validate/validate_values_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package validate 16 17 import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 "testing" 22 23 "sigs.k8s.io/yaml" 24 25 "istio.io/istio/operator/pkg/helm" 26 "istio.io/istio/operator/pkg/object" 27 "istio.io/istio/operator/pkg/util" 28 "istio.io/istio/pkg/test/env" 29 ) 30 31 var repoRootDir string 32 33 func init() { 34 repoRootDir = env.IstioSrc 35 } 36 37 func TestValidateValues(t *testing.T) { 38 tests := []struct { 39 desc string 40 yamlStr string 41 wantErrs util.Errors 42 }{ 43 { 44 desc: "nil success", 45 }, 46 { 47 desc: "StarIPRange", 48 yamlStr: ` 49 global: 50 proxy: 51 includeIPRanges: "*" 52 excludeIPRanges: "*" 53 `, 54 }, 55 { 56 desc: "ProxyConfig", 57 yamlStr: ` 58 global: 59 podDNSSearchNamespaces: 60 - "my-namespace" 61 proxy: 62 includeIPRanges: "1.1.0.0/16,2.2.0.0/16" 63 excludeIPRanges: "3.3.0.0/16,4.4.0.0/16" 64 excludeInboundPorts: "333,444" 65 clusterDomain: "my.domain" 66 lifecycle: 67 preStop: 68 exec: 69 command: ["/bin/sh", "-c", "sleep 30"] 70 `, 71 }, 72 { 73 desc: "CNIConfig", 74 yamlStr: ` 75 cni: 76 cniBinDir: "/var/lib/cni/bin" 77 cniConfDir: "/var/run/multus/cni/net.d" 78 `, 79 }, 80 81 { 82 desc: "BadIPRange", 83 yamlStr: ` 84 global: 85 proxy: 86 includeIPRanges: "1.1.0.256/16,2.2.0.257/16" 87 excludeIPRanges: "3.3.0.0/33,4.4.0.0/34" 88 `, 89 wantErrs: makeErrors([]string{ 90 `global.proxy.excludeIPRanges netip.ParsePrefix("3.3.0.0/33"): prefix length out of range`, 91 `global.proxy.excludeIPRanges netip.ParsePrefix("4.4.0.0/34"): prefix length out of range`, 92 `global.proxy.includeIPRanges netip.ParsePrefix("1.1.0.256/16"): ParseAddr("1.1.0.256"): IPv4 field has value >255`, 93 `global.proxy.includeIPRanges netip.ParsePrefix("2.2.0.257/16"): ParseAddr("2.2.0.257"): IPv4 field has value >255`, 94 }), 95 }, 96 { 97 desc: "BadIPMalformed", 98 yamlStr: ` 99 global: 100 proxy: 101 includeIPRanges: "1.2.3/16,1.2.3.x/16" 102 `, 103 wantErrs: makeErrors([]string{ 104 `global.proxy.includeIPRanges netip.ParsePrefix("1.2.3/16"): ParseAddr("1.2.3"): IPv4 address too short`, 105 `global.proxy.includeIPRanges netip.ParsePrefix("1.2.3.x/16"): ParseAddr("1.2.3.x"): unexpected character (at "x")`, 106 }), 107 }, 108 { 109 desc: "BadIPWithStar", 110 yamlStr: ` 111 global: 112 proxy: 113 includeIPRanges: "*,1.1.0.0/16,2.2.0.0/16" 114 `, 115 wantErrs: makeErrors([]string{`global.proxy.includeIPRanges netip.ParsePrefix("*"): no '/'`}), 116 }, 117 { 118 desc: "BadPortRange", 119 yamlStr: ` 120 global: 121 proxy: 122 excludeInboundPorts: "-1,444" 123 `, 124 wantErrs: makeErrors([]string{`value global.proxy.excludeInboundPorts:-1 falls outside range [0, 65535]`}), 125 }, 126 { 127 desc: "BadPortMalformed", 128 yamlStr: ` 129 global: 130 proxy: 131 excludeInboundPorts: "111,222x" 132 `, 133 wantErrs: makeErrors([]string{`global.proxy.excludeInboundPorts : strconv.ParseInt: parsing "222x": invalid syntax`}), 134 }, 135 { 136 desc: "unknown field", 137 yamlStr: ` 138 global: 139 proxy: 140 foo: "bar" 141 `, 142 wantErrs: makeErrors([]string{`unknown field "foo" in v1alpha1.ProxyConfig`}), 143 }, 144 { 145 desc: "unknown cni field", 146 yamlStr: ` 147 cni: 148 foo: "bar" 149 `, 150 wantErrs: makeErrors([]string{`unknown field "foo" in v1alpha1.CNIConfig`}), 151 }, 152 } 153 154 for _, tt := range tests { 155 t.Run(tt.desc, func(t *testing.T) { 156 root := make(map[string]any) 157 err := yaml.Unmarshal([]byte(tt.yamlStr), &root) 158 if err != nil { 159 t.Fatalf("yaml.Unmarshal(%s): got error %s", tt.desc, err) 160 } 161 errs := CheckValues(util.MustStruct(root)) 162 if gotErr, wantErr := errs, tt.wantErrs; !util.EqualErrors(gotErr, wantErr) { 163 t.Errorf("CheckValues(%s)(%v): gotErr:%s, wantErr:%s", tt.desc, tt.yamlStr, gotErr, wantErr) 164 } 165 }) 166 } 167 } 168 169 func TestValidateValuesFromProfile(t *testing.T) { 170 tests := []struct { 171 profile string 172 wantErrs util.Errors 173 }{ 174 { 175 profile: "default", 176 }, 177 { 178 profile: "demo", 179 }, 180 { 181 profile: "minimal", 182 }, 183 } 184 for _, tt := range tests { 185 t.Run(tt.profile, func(t *testing.T) { 186 pf, err := helm.ReadProfileYAML(tt.profile, filepath.Join(env.IstioSrc, "manifests")) 187 if err != nil { 188 t.Fatalf("fail to read profile: %s", tt.profile) 189 } 190 val, _, err := object.ParseK8SYAMLToIstioOperator(pf) 191 if err != nil { 192 t.Fatalf(" fail to parse profile to ISCP: (%s), got error %s", tt.profile, err) 193 } 194 errs := CheckValues(val.Spec.Values) 195 if gotErr, wantErr := errs, tt.wantErrs; !util.EqualErrors(gotErr, wantErr) { 196 t.Errorf("CheckValues of (%v): gotErr:%s, wantErr:%s", tt.profile, gotErr, wantErr) 197 } 198 }) 199 } 200 } 201 202 func TestValidateValuesFromValuesYAMLs(t *testing.T) { 203 valuesYAML := "" 204 var allFiles []string 205 manifestDir := filepath.Join(repoRootDir, "manifests/charts") 206 for _, sd := range []string{"base", "gateways", "istio-cni", "istio-control"} { 207 dir := filepath.Join(manifestDir, sd) 208 files, err := util.FindFiles(dir, yamlFileFilter) 209 if err != nil { 210 t.Fatalf(err.Error()) 211 } 212 allFiles = append(allFiles, files...) 213 } 214 for _, f := range allFiles { 215 b, err := os.ReadFile(f) 216 if err != nil { 217 t.Fatal(err.Error()) 218 } 219 valuesYAML, err = util.OverlayYAML(valuesYAML, string(b)) 220 if err != nil { 221 t.Fatal(err.Error()) 222 } 223 valuesTree := make(map[string]any) 224 if err := yaml.Unmarshal([]byte(valuesYAML), &valuesTree); err != nil { 225 t.Fatal(err.Error()) 226 } 227 if err := CheckValues(util.MustStruct(valuesTree["defaults"].(map[string]any))); err != nil { 228 t.Fatalf("file %s failed validation with: %s", f, err) 229 } 230 } 231 } 232 233 func makeErrors(estr []string) util.Errors { 234 var errs util.Errors 235 for _, s := range estr { 236 errs = util.AppendErr(errs, fmt.Errorf("%s", s)) 237 } 238 return errs 239 } 240 241 func yamlFileFilter(path string) bool { 242 return filepath.Base(path) == "values.yaml" 243 }