istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/local/context.go (about) 1 /* 2 Copyright Istio 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 local 18 19 import ( 20 "encoding/json" 21 "fmt" 22 23 "istio.io/istio/pilot/pkg/config/file" 24 "istio.io/istio/pilot/pkg/model" 25 "istio.io/istio/pkg/cluster" 26 "istio.io/istio/pkg/config" 27 "istio.io/istio/pkg/config/analysis" 28 "istio.io/istio/pkg/config/analysis/diag" 29 "istio.io/istio/pkg/config/analysis/legacy/source/kube" 30 "istio.io/istio/pkg/config/resource" 31 "istio.io/istio/pkg/config/schema/collections" 32 sresource "istio.io/istio/pkg/config/schema/resource" 33 "istio.io/istio/pkg/log" 34 ) 35 36 // NewContext allows tests to use istiodContext without exporting it. returned context is not threadsafe. 37 func NewContext(stores map[cluster.ID]model.ConfigStore, cancelCh <-chan struct{}, collectionReporter CollectionReporterFn) analysis.Context { 38 return &istiodContext{ 39 stores: stores, 40 cancelCh: cancelCh, 41 messages: map[string]*diag.Messages{}, 42 collectionReporter: collectionReporter, 43 found: map[key]*resource.Instance{}, 44 foundCollections: map[config.GroupVersionKind]map[key]*resource.Instance{}, 45 } 46 } 47 48 type istiodContext struct { 49 stores map[cluster.ID]model.ConfigStore 50 cancelCh <-chan struct{} 51 messages map[string]*diag.Messages 52 collectionReporter CollectionReporterFn 53 found map[key]*resource.Instance 54 foundCollections map[config.GroupVersionKind]map[key]*resource.Instance 55 currentAnalyzer string 56 } 57 58 type key struct { 59 collectionName config.GroupVersionKind 60 name resource.FullName 61 cluster cluster.ID 62 } 63 64 func (i *istiodContext) Report(c config.GroupVersionKind, m diag.Message) { 65 msgs := i.messages[i.currentAnalyzer] 66 if msgs == nil { 67 msgs = &diag.Messages{} 68 i.messages[i.currentAnalyzer] = msgs 69 } 70 msgs.Add(m) 71 } 72 73 func (i *istiodContext) SetAnalyzer(analyzerName string) { 74 i.currentAnalyzer = analyzerName 75 } 76 77 func (i *istiodContext) GetMessages(analyzerNames ...string) diag.Messages { 78 result := diag.Messages{} 79 if len(analyzerNames) == 0 { 80 // no AnalyzerNames is equivalent to a wildcard, requesting all messages. 81 for _, msgs := range i.messages { 82 result.Add(*msgs...) 83 } 84 } else { 85 for _, name := range analyzerNames { 86 if msgs, ok := i.messages[name]; ok { 87 result.Add(*msgs...) 88 } 89 } 90 } 91 return result 92 } 93 94 func (i *istiodContext) Find(col config.GroupVersionKind, name resource.FullName) *resource.Instance { 95 i.collectionReporter(col) 96 k := key{col, name, "default"} 97 if result, ok := i.found[k]; ok { 98 return result 99 } 100 if cache, ok := i.foundCollections[col]; ok { 101 if result, ok2 := cache[k]; ok2 { 102 return result 103 } 104 } 105 colschema, ok := collections.All.FindByGroupVersionKind(col) 106 if !ok { 107 log.Warnf("collection %s could not be found", col.String()) 108 return nil 109 } 110 for id, store := range i.stores { 111 cfg := store.Get(colschema.GroupVersionKind(), name.Name.String(), name.Namespace.String()) 112 if cfg == nil { 113 continue 114 } 115 result, err := cfgToInstance(*cfg, col, colschema, id) 116 if err != nil { 117 log.Errorf("failed converting found config %s %s/%s to instance: %s, ", 118 cfg.Meta.GroupVersionKind.Kind, cfg.Meta.Namespace, cfg.Meta.Namespace, err) 119 return nil 120 } 121 i.found[k] = result 122 return result 123 } 124 return nil 125 } 126 127 func (i *istiodContext) Exists(col config.GroupVersionKind, name resource.FullName) bool { 128 i.collectionReporter(col) 129 return i.Find(col, name) != nil 130 } 131 132 func (i *istiodContext) ForEach(col config.GroupVersionKind, fn analysis.IteratorFn) { 133 if i.collectionReporter != nil { 134 i.collectionReporter(col) 135 } 136 if cached, ok := i.foundCollections[col]; ok { 137 for _, res := range cached { 138 if !fn(res) { 139 break 140 } 141 } 142 return 143 } 144 colschema, ok := collections.All.FindByGroupVersionKind(col) 145 if !ok { 146 // TODO: demote this log before merging 147 log.Errorf("collection %s could not be found", col.String()) 148 return 149 } 150 cache := map[key]*resource.Instance{} 151 for id, store := range i.stores { 152 // TODO: this needs to include file source as well 153 cfgs := store.List(colschema.GroupVersionKind(), "") 154 broken := false 155 for _, cfg := range cfgs { 156 k := key{ 157 col, resource.FullName{ 158 Name: resource.LocalName(cfg.Name), 159 Namespace: resource.Namespace(cfg.Namespace), 160 }, id, 161 } 162 if res, ok := i.found[k]; ok { 163 if !broken && !fn(res) { 164 broken = true 165 } 166 cache[k] = res 167 continue 168 } 169 res, err := cfgToInstance(cfg, col, colschema, id) 170 if err != nil { 171 // TODO: demote this log before merging 172 log.Error(err) 173 // TODO: is continuing the right thing here? 174 continue 175 } 176 if !broken && !fn(res) { 177 broken = true 178 } 179 cache[k] = res 180 } 181 if len(cache) > 0 { 182 i.foundCollections[col] = cache 183 } 184 } 185 } 186 187 func (i *istiodContext) Canceled() bool { 188 select { 189 case <-i.cancelCh: 190 return true 191 default: 192 return false 193 } 194 } 195 196 func cfgToInstance(cfg config.Config, col config.GroupVersionKind, colschema sresource.Schema, cluster cluster.ID) (*resource.Instance, 197 error, 198 ) { 199 res := resource.PilotConfigToInstance(&cfg, colschema) 200 fmstring := cfg.Meta.Annotations[file.FieldMapKey] 201 var out map[string]int 202 if fmstring != "" { 203 err := json.Unmarshal([]byte(fmstring), &out) 204 if err != nil { 205 return nil, fmt.Errorf("error parsing fieldmap: %s", err) 206 } 207 } 208 refstring := cfg.Meta.Annotations[file.ReferenceKey] 209 var outref resource.Reference 210 if refstring != "" { 211 outref = &kube.Position{} 212 err := json.Unmarshal([]byte(refstring), outref) 213 if err != nil { 214 return nil, fmt.Errorf("error parsing reference: %s", err) 215 } 216 } 217 res.Origin = &kube.Origin{ 218 Type: col, 219 FullName: res.Metadata.FullName, 220 ResourceVersion: resource.Version(cfg.ResourceVersion), 221 Ref: outref, 222 FieldsMap: out, 223 Cluster: cluster, 224 } 225 // MCP is not aware of generation, add that here. 226 res.Metadata.Generation = cfg.Generation 227 return res, nil 228 }