istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/echo/config/builder.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 config 16 17 import ( 18 "fmt" 19 "reflect" 20 "time" 21 22 "istio.io/istio/pkg/test/framework" 23 "istio.io/istio/pkg/test/framework/components/echo" 24 "istio.io/istio/pkg/test/framework/components/echo/config/param" 25 "istio.io/istio/pkg/test/framework/components/istio" 26 "istio.io/istio/pkg/test/framework/components/namespace" 27 "istio.io/istio/pkg/test/framework/resource/config" 28 "istio.io/istio/pkg/test/framework/resource/config/apply" 29 "istio.io/istio/pkg/test/scopes" 30 "istio.io/istio/pkg/util/sets" 31 ) 32 33 // Builder of configuration. 34 type Builder struct { 35 t framework.TestContext 36 out config.Plan 37 needFrom []Source 38 needTo []Source 39 needFromAndTo []Source 40 complete []Source 41 yamlCount int 42 } 43 44 func New(t framework.TestContext) *Builder { 45 return NewWithOutput(t, t.ConfigIstio().New()) 46 } 47 48 // NewWithOutput creates a new Builder that targets the given config as output. 49 func NewWithOutput(t framework.TestContext, out config.Plan) *Builder { 50 t.Helper() 51 checkNotNil("t", t) 52 checkNotNil("out", out) 53 54 return &Builder{ 55 t: t, 56 out: out, 57 } 58 } 59 60 func checkNotNil(name string, value any) { 61 if value == nil { 62 panic(fmt.Sprintf("%s must not be nil", name)) 63 } 64 } 65 66 // Output returns a copy of this Builder with the given output set. 67 func (b *Builder) Output(out config.Plan) *Builder { 68 b.t.Helper() 69 70 checkNotNil("out", out) 71 72 ret := b.Copy() 73 ret.out = out 74 return ret 75 } 76 77 // Context returns a copy of this Builder with the given context set. 78 func (b *Builder) Context(t framework.TestContext) *Builder { 79 checkNotNil("t", t) 80 out := b.Copy() 81 out.t = t 82 return out 83 } 84 85 // Source returns a copy of this Builder with the given Source objects added. 86 func (b *Builder) Source(sources ...Source) *Builder { 87 b.t.Helper() 88 out := b.Copy() 89 90 for _, s := range sources { 91 // Split the source so that we process a single CRD at a time. 92 for _, s := range s.SplitOrFail(b.t) { 93 // If the caller set namespaces with literal strings, replace with namespace.Instance objects. 94 s = replaceNamespaceStrings(s) 95 96 tpl := s.TemplateOrFail(out.t) 97 need := tpl.MissingParams(s.Params()) 98 needFrom := need.Contains(param.From.String()) 99 needTo := need.Contains(param.To.String()) 100 101 if needFrom && needTo { 102 out.needFromAndTo = append(out.needFromAndTo, s) 103 } else if needFrom { 104 out.needFrom = append(out.needFrom, s) 105 } else if needTo { 106 out.needTo = append(out.needTo, s) 107 } else { 108 // No well-known parameters are missing. 109 out.complete = append(out.complete, s) 110 } 111 112 // Delete all the wellknown parameters. 113 need.DeleteAll(param.AllWellKnown().ToStringArray()...) 114 if len(need) > 0 { 115 panic(fmt.Sprintf("config source missing parameters: %v", need)) 116 } 117 } 118 } 119 120 return out 121 } 122 123 // BuildCompleteSources builds only those sources that already contain all parameters 124 // needed by their templates. Specifically, they are not missing any of the well-known 125 // parameters: "From", "To", or "Namespace". 126 func (b *Builder) BuildCompleteSources() *Builder { 127 b.t.Helper() 128 out := b.Copy() 129 130 systemNS := istio.ClaimSystemNamespaceOrFail(out.t, out.t) 131 132 // Build all the complete config that doesn't require well-known parameters. 133 for _, s := range out.complete { 134 out.addYAML(withParams(s, param.Params{ 135 param.SystemNamespace.String(): systemNS, 136 })) 137 } 138 139 return out 140 } 141 142 // BuildFrom builds only those sources that require only the "From" parameter. 143 func (b *Builder) BuildFrom(fromAll ...echo.Caller) *Builder { 144 b.t.Helper() 145 out := b.Copy() 146 147 systemNS := istio.ClaimSystemNamespaceOrFail(out.t, out.t) 148 149 for _, from := range fromAll { 150 for _, s := range out.needFrom { 151 out.addYAML(withParams(s, param.Params{ 152 param.From.String(): from, 153 param.SystemNamespace.String(): systemNS, 154 })) 155 } 156 } 157 158 return out 159 } 160 161 // BuildFromAndTo builds only those sources that require both the "From" and "To" parameters. 162 func (b *Builder) BuildFromAndTo(fromAll echo.Callers, toAll echo.Services) *Builder { 163 b.t.Helper() 164 out := b.Copy() 165 166 systemNS := istio.ClaimSystemNamespaceOrFail(out.t, out.t) 167 168 for _, from := range fromAll { 169 for _, to := range toAll { 170 for _, s := range out.needFromAndTo { 171 out.addYAML(withParams(s, param.Params{ 172 param.From.String(): from, 173 param.To.String(): to, 174 param.Namespace.String(): to.Config().Namespace, 175 param.SystemNamespace.String(): systemNS, 176 })) 177 } 178 } 179 } 180 181 for _, to := range toAll { 182 for _, s := range out.needTo { 183 out.addYAML(withParams(s, param.Params{ 184 param.To.String(): to, 185 param.Namespace.String(): to.Config().Namespace, 186 param.SystemNamespace.String(): systemNS, 187 })) 188 } 189 } 190 191 return out 192 } 193 194 // BuildAll builds the config for all sources. 195 func (b *Builder) BuildAll(fromAll echo.Callers, toAll echo.Services) *Builder { 196 b.t.Helper() 197 198 return b.BuildCompleteSources(). 199 BuildFrom(fromAll...). 200 BuildFromAndTo(fromAll, toAll) 201 } 202 203 func (b *Builder) Apply(opts ...apply.Option) { 204 if b.yamlCount == 0 { 205 // Nothing to do. 206 return 207 } 208 209 start := time.Now() 210 scopes.Framework.Info("=== BEGIN: Deploy config ===") 211 212 b.out.ApplyOrFail(b.t, opts...) 213 214 scopes.Framework.Infof("=== SUCCEEDED: Deploy config in %v ===", time.Since(start)) 215 } 216 217 // Replace any namespace strings with namespace.Instance objects. 218 func replaceNamespaceStrings(s Source) Source { 219 params := s.Params() 220 newParams := param.NewParams() 221 for _, nsKey := range []param.WellKnown{param.Namespace, param.SystemNamespace} { 222 if params.ContainsWellKnown(nsKey) { 223 val := params.GetWellKnown(nsKey) 224 if strVal, ok := val.(string); ok { 225 newParams.SetWellKnown(nsKey, namespace.Static(strVal)) 226 } 227 } 228 } 229 return s.WithParams(newParams) 230 } 231 232 func (b *Builder) addYAML(s Source) { 233 b.t.Helper() 234 235 // Ensure all parameters have been set. 236 b.checkMissing(s) 237 238 // Get the namespace where the config should be applied. 239 ns := b.getNamespace(s) 240 241 // Generate the YAML and add it to the configuration. 242 b.out.YAML(ns.Name(), s.YAMLOrFail(b.t)) 243 b.yamlCount++ 244 } 245 246 func withParams(s Source, params param.Params) Source { 247 return s.WithParams(s.Params().SetAllNoOverwrite(params)) 248 } 249 250 func (b *Builder) checkMissing(s Source) { 251 b.t.Helper() 252 tpl := s.TemplateOrFail(b.t) 253 missing := tpl.MissingParams(s.Params()) 254 if missing.Len() > 0 { 255 b.t.Fatalf("config template requires missing params: %v", sets.SortedList(missing)) 256 } 257 } 258 259 func (b *Builder) getNamespace(s Source) namespace.Instance { 260 b.t.Helper() 261 ns := s.Params().GetWellKnown(param.Namespace) 262 if ns == nil { 263 b.t.Fatalf("no %s specified in config params", param.Namespace) 264 } 265 inst, ok := ns.(namespace.Instance) 266 if !ok { 267 b.t.Fatalf("%s was of unexpected type: %v", param.Namespace, reflect.TypeOf(ns)) 268 } 269 return inst 270 } 271 272 func (b *Builder) Copy() *Builder { 273 return &Builder{ 274 t: b.t, 275 needFrom: copySources(b.needFrom), 276 needTo: copySources(b.needTo), 277 needFromAndTo: copySources(b.needFromAndTo), 278 complete: copySources(b.complete), 279 out: b.out.Copy(), 280 yamlCount: b.yamlCount, 281 } 282 } 283 284 func copySources(sources []Source) []Source { 285 return append([]Source{}, sources...) 286 }