istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/local/istiod_analyze.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 local 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "os" 22 "strings" 23 24 "github.com/hashicorp/go-multierror" 25 "github.com/ryanuber/go-glob" 26 v1 "k8s.io/api/core/v1" 27 kerrors "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/fields" 30 31 "istio.io/api/annotation" 32 "istio.io/api/mesh/v1alpha1" 33 "istio.io/istio/pilot/pkg/config/aggregate" 34 "istio.io/istio/pilot/pkg/config/file" 35 "istio.io/istio/pilot/pkg/config/kube/crdclient" 36 "istio.io/istio/pilot/pkg/config/memory" 37 "istio.io/istio/pilot/pkg/leaderelection/k8sleaderelection/k8sresourcelock" 38 "istio.io/istio/pilot/pkg/model" 39 "istio.io/istio/pkg/cluster" 40 "istio.io/istio/pkg/config" 41 "istio.io/istio/pkg/config/analysis" 42 "istio.io/istio/pkg/config/analysis/diag" 43 "istio.io/istio/pkg/config/analysis/legacy/util/kuberesource" 44 "istio.io/istio/pkg/config/analysis/scope" 45 "istio.io/istio/pkg/config/mesh" 46 "istio.io/istio/pkg/config/resource" 47 "istio.io/istio/pkg/config/schema/collection" 48 "istio.io/istio/pkg/config/schema/collections" 49 "istio.io/istio/pkg/config/schema/gvk" 50 kubelib "istio.io/istio/pkg/kube" 51 "istio.io/istio/pkg/kube/inject" 52 "istio.io/istio/pkg/kube/kubetypes" 53 "istio.io/istio/pkg/util/sets" 54 ) 55 56 // IstiodAnalyzer handles local analysis of k8s event sources, both live and file-based 57 type IstiodAnalyzer struct { 58 // internalStore stores synthetic configs for analysis (mesh config, etc) 59 internalStore model.ConfigStore 60 // stores contains all the (non file) config sources to analyze 61 stores []model.ConfigStoreController 62 // multiClusterStores contains all the multi-cluster config sources to analyze 63 multiClusterStores map[cluster.ID]model.ConfigStoreController 64 // cluster is the cluster ID for the environment we are analyzing 65 cluster cluster.ID 66 // fileSource contains all file bases sources 67 fileSource *file.KubeSource 68 69 analyzer analysis.CombinedAnalyzer 70 namespace resource.Namespace 71 istioNamespace resource.Namespace 72 73 // List of code and resource suppressions to exclude messages on 74 suppressions []AnalysisSuppression 75 76 // Mesh config for this analyzer. This can come from multiple sources, and the last added version will take precedence. 77 meshCfg *v1alpha1.MeshConfig 78 79 // Mesh networks config for this analyzer. 80 meshNetworks *v1alpha1.MeshNetworks 81 82 // Which kube resources are used by this analyzer 83 // Derived from metadata and the specified analyzer and transformer providers 84 kubeResources collection.Schemas 85 86 // Hook function called when a collection is used in analysis 87 collectionReporter CollectionReporterFn 88 89 clientsToRun []kubelib.Client 90 } 91 92 // NewSourceAnalyzer is a drop-in replacement for the galley function, adapting to istiod analyzer. 93 func NewSourceAnalyzer(analyzer analysis.CombinedAnalyzer, namespace, istioNamespace resource.Namespace, cr CollectionReporterFn) *IstiodAnalyzer { 94 return NewIstiodAnalyzer(analyzer, namespace, istioNamespace, cr) 95 } 96 97 // NewIstiodAnalyzer creates a new IstiodAnalyzer with no sources. Use the Add*Source 98 // methods to add sources in ascending precedence order, 99 // then execute Analyze to perform the analysis 100 func NewIstiodAnalyzer(analyzer analysis.CombinedAnalyzer, namespace, 101 istioNamespace resource.Namespace, cr CollectionReporterFn, 102 ) *IstiodAnalyzer { 103 // collectionReporter hook function defaults to no-op 104 if cr == nil { 105 cr = func(config.GroupVersionKind) {} 106 } 107 108 // Get the closure of all input collections for our analyzer, paying attention to transforms 109 kubeResources := kuberesource.ConvertInputsToSchemas(analyzer.Metadata().Inputs) 110 111 kubeResources = kubeResources.Union(kuberesource.DefaultExcludedSchemas()) 112 113 mcfg := mesh.DefaultMeshConfig() 114 sa := &IstiodAnalyzer{ 115 meshCfg: mcfg, 116 meshNetworks: mesh.DefaultMeshNetworks(), 117 analyzer: analyzer, 118 namespace: namespace, 119 cluster: "default", 120 internalStore: memory.Make(collection.SchemasFor(collections.MeshNetworks, collections.MeshConfig)), 121 istioNamespace: istioNamespace, 122 kubeResources: kubeResources, 123 collectionReporter: cr, 124 clientsToRun: []kubelib.Client{}, 125 multiClusterStores: make(map[cluster.ID]model.ConfigStoreController), 126 } 127 128 return sa 129 } 130 131 func (sa *IstiodAnalyzer) ReAnalyzeSubset(kinds sets.Set[config.GroupVersionKind], cancel <-chan struct{}) (AnalysisResult, error) { 132 subset := sa.analyzer.RelevantSubset(kinds) 133 return sa.internalAnalyze(subset, cancel) 134 } 135 136 // ReAnalyze loads the sources and executes the analysis, assuming init is already called 137 func (sa *IstiodAnalyzer) ReAnalyze(cancel <-chan struct{}) (AnalysisResult, error) { 138 return sa.internalAnalyze(sa.analyzer, cancel) 139 } 140 141 func (sa *IstiodAnalyzer) internalAnalyze(a analysis.CombinedAnalyzer, cancel <-chan struct{}) (AnalysisResult, error) { 142 var schemas collection.Schemas 143 for _, store := range sa.multiClusterStores { 144 schemas = schemas.Union(store.Schemas()) 145 } 146 147 var result AnalysisResult 148 result.ExecutedAnalyzers = a.AnalyzerNames() 149 result.SkippedAnalyzers = a.RemoveSkipped(schemas) 150 result.MappedMessages = make(map[string]diag.Messages, len(result.ExecutedAnalyzers)) 151 152 for _, store := range sa.multiClusterStores { 153 kubelib.WaitForCacheSync("istiod analyzer", cancel, store.HasSynced) 154 } 155 156 stores := map[cluster.ID]model.ConfigStore{} 157 for k, v := range sa.multiClusterStores { 158 stores[k] = v 159 } 160 ctx := NewContext(stores, cancel, sa.collectionReporter) 161 162 a.Analyze(ctx) 163 164 // TODO(hzxuzhonghu): we do not need set here 165 namespaces := sets.New[resource.Namespace]() 166 if sa.namespace != "" { 167 namespaces.Insert(sa.namespace) 168 } 169 for _, analyzerName := range result.ExecutedAnalyzers { 170 171 // TODO: analysis is run for all namespaces, even if they are requested to be filtered. 172 msgs := filterMessages(ctx.(*istiodContext).GetMessages(analyzerName), namespaces, sa.suppressions) 173 result.MappedMessages[analyzerName] = msgs.SortedDedupedCopy() 174 } 175 msgs := filterMessages(ctx.(*istiodContext).GetMessages(), namespaces, sa.suppressions) 176 result.Messages = msgs.SortedDedupedCopy() 177 178 return result, nil 179 } 180 181 // Analyze loads the sources and executes the analysis 182 func (sa *IstiodAnalyzer) Analyze(cancel <-chan struct{}) (AnalysisResult, error) { 183 err2 := sa.Init(cancel) 184 if err2 != nil { 185 return AnalysisResult{}, err2 186 } 187 return sa.ReAnalyze(cancel) 188 } 189 190 func (sa *IstiodAnalyzer) Init(cancel <-chan struct{}) error { 191 // We need at least one non-meshcfg source 192 if len(sa.stores) == 0 && sa.fileSource == nil { 193 return fmt.Errorf("at least one file and/or Kubernetes source must be provided") 194 } 195 196 // TODO: there's gotta be a better way to convert v1meshconfig to config.Config... 197 // Create a store containing mesh config. There should be exactly one. 198 _, err := sa.internalStore.Create(config.Config{ 199 Meta: config.Meta{ 200 Name: "meshconfig", 201 Namespace: sa.istioNamespace.String(), 202 203 GroupVersionKind: gvk.MeshConfig, 204 }, 205 Spec: sa.meshCfg, 206 }) 207 if err != nil { 208 return fmt.Errorf("something unexpected happened while creating the meshconfig: %s", err) 209 } 210 // Create a store containing meshnetworks. There should be exactly one. 211 _, err = sa.internalStore.Create(config.Config{ 212 Meta: config.Meta{ 213 Name: "meshnetworks", 214 Namespace: sa.istioNamespace.String(), 215 GroupVersionKind: gvk.MeshNetworks, 216 }, 217 Spec: sa.meshNetworks, 218 }) 219 if err != nil { 220 return fmt.Errorf("something unexpected happened while creating the meshnetworks: %s", err) 221 } 222 allstores := append(sa.stores, dfCache{ConfigStore: sa.internalStore}) 223 if sa.fileSource != nil { 224 // File source takes the highest precedence, since files are resources to be configured to in-cluster resources. 225 // The order here does matter - aggregated store takes the first available resource. 226 allstores = append([]model.ConfigStoreController{sa.fileSource}, allstores...) 227 } 228 229 for _, c := range sa.clientsToRun { 230 // TODO: this could be parallel 231 c.RunAndWait(cancel) 232 } 233 234 store, err := aggregate.MakeWriteableCache(allstores, nil) 235 if err != nil { 236 return err 237 } 238 sa.multiClusterStores[sa.cluster] = store 239 for _, mcs := range sa.multiClusterStores { 240 go mcs.Run(cancel) 241 } 242 return nil 243 } 244 245 type dfCache struct { 246 model.ConfigStore 247 } 248 249 func (d dfCache) RegisterEventHandler(kind config.GroupVersionKind, handler model.EventHandler) { 250 panic("implement me") 251 } 252 253 // Run intentionally left empty 254 func (d dfCache) Run(_ <-chan struct{}) { 255 } 256 257 func (d dfCache) HasSynced() bool { 258 return true 259 } 260 261 // SetSuppressions will set the list of suppressions for the analyzer. Any 262 // resource that matches the provided suppression will not be included in the 263 // final message output. 264 func (sa *IstiodAnalyzer) SetSuppressions(suppressions []AnalysisSuppression) { 265 sa.suppressions = suppressions 266 } 267 268 // AddTestReaderKubeSource adds a yaml source to the analyzer, which will analyze 269 // runtime resources like pods and namespaces for use in tests. 270 func (sa *IstiodAnalyzer) AddTestReaderKubeSource(readers []ReaderSource) error { 271 return sa.addReaderKubeSourceInternal(readers, true) 272 } 273 274 // AddReaderKubeSource adds a source based on the specified k8s yaml files to the current IstiodAnalyzer 275 func (sa *IstiodAnalyzer) AddReaderKubeSource(readers []ReaderSource) error { 276 return sa.addReaderKubeSourceInternal(readers, false) 277 } 278 279 func (sa *IstiodAnalyzer) addReaderKubeSourceInternal(readers []ReaderSource, includeRuntimeResources bool) error { 280 var src *file.KubeSource 281 if sa.fileSource != nil { 282 src = sa.fileSource 283 } else { 284 var readerResources collection.Schemas 285 if includeRuntimeResources { 286 readerResources = sa.kubeResources 287 } else { 288 readerResources = sa.kubeResources.Remove(kuberesource.DefaultExcludedSchemas().All()...) 289 } 290 src = file.NewKubeSource(readerResources) 291 sa.fileSource = src 292 } 293 src.SetDefaultNamespace(sa.namespace) 294 295 src.SetNamespacesFilter(func(obj interface{}) bool { 296 cfg, ok := obj.(config.Config) 297 if !ok { 298 return false 299 } 300 meta := cfg.GetNamespace() 301 if cfg.Meta.GroupVersionKind.Kind == gvk.Namespace.Kind { 302 meta = cfg.GetName() 303 } 304 return !inject.IgnoredNamespaces.Contains(meta) 305 }) 306 307 var errs error 308 309 // If we encounter any errors reading or applying files, track them but attempt to continue 310 for _, r := range readers { 311 by, err := io.ReadAll(r.Reader) 312 if err != nil { 313 errs = multierror.Append(errs, err) 314 continue 315 } 316 317 if err = src.ApplyContent(r.Name, string(by)); err != nil { 318 errs = multierror.Append(errs, err) 319 } 320 } 321 return errs 322 } 323 324 // AddRunningKubeSource adds a source based on a running k8s cluster to the current IstiodAnalyzer 325 // Also tries to get mesh config from the running cluster, if it can 326 func (sa *IstiodAnalyzer) AddRunningKubeSource(c kubelib.Client) { 327 sa.AddRunningKubeSourceWithRevision(c, "default", false) 328 } 329 330 func isIstioConfigMap(obj any) bool { 331 cObj, ok := obj.(*v1.ConfigMap) 332 if !ok { 333 return false 334 } 335 if _, ok = cObj.GetAnnotations()[k8sresourcelock.LeaderElectionRecordAnnotationKey]; ok { 336 return false 337 } 338 return strings.HasPrefix(cObj.GetName(), "istio") 339 } 340 341 var secretFieldSelector = fields.AndSelectors( 342 fields.OneTermNotEqualSelector("type", "helm.sh/release.v1"), 343 fields.OneTermNotEqualSelector("type", string(v1.SecretTypeServiceAccountToken))).String() 344 345 func (sa *IstiodAnalyzer) GetFiltersByGVK() map[config.GroupVersionKind]kubetypes.Filter { 346 return map[config.GroupVersionKind]kubetypes.Filter{ 347 gvk.ConfigMap: { 348 Namespace: sa.istioNamespace.String(), 349 ObjectFilter: kubetypes.NewStaticObjectFilter(isIstioConfigMap), 350 }, 351 gvk.Secret: { 352 FieldSelector: secretFieldSelector, 353 }, 354 } 355 } 356 357 func (sa *IstiodAnalyzer) AddRunningKubeSourceWithRevision(c kubelib.Client, revision string, remote bool) { 358 // This makes the assumption we don't care about Helm secrets or SA token secrets - two common 359 // large secrets in clusters. 360 // This is a best effort optimization only; the code would behave correctly if we watched all secrets. 361 362 ignoredNamespacesSelectorForField := func(field string) string { 363 selectors := make([]fields.Selector, 0, len(inject.IgnoredNamespaces)) 364 for _, ns := range inject.IgnoredNamespaces.UnsortedList() { 365 selectors = append(selectors, fields.OneTermNotEqualSelector(field, ns)) 366 } 367 return fields.AndSelectors(selectors...).String() 368 } 369 370 namespaceFieldSelector := ignoredNamespacesSelectorForField("metadata.name") 371 generalSelectors := ignoredNamespacesSelectorForField("metadata.namespace") 372 373 // TODO: are either of these string constants intended to vary? 374 // We gets Istio CRD resources with a specific revision. 375 krs := sa.kubeResources.Remove(kuberesource.DefaultExcludedSchemas().All()...) 376 if remote { 377 krs = krs.Remove(kuberesource.DefaultRemoteClusterExcludedSchemas().All()...) 378 } 379 store := crdclient.NewForSchemas(c, crdclient.Option{ 380 Revision: revision, 381 DomainSuffix: "cluster.local", 382 Identifier: "analysis-controller", 383 FiltersByGVK: map[config.GroupVersionKind]kubetypes.Filter{ 384 gvk.ConfigMap: { 385 Namespace: sa.istioNamespace.String(), 386 ObjectFilter: kubetypes.NewStaticObjectFilter(isIstioConfigMap), 387 }, 388 }, 389 }, krs) 390 sa.stores = append(sa.stores, store) 391 392 // We gets service discovery resources without a specific revision. 393 krs = sa.kubeResources.Intersect(kuberesource.DefaultExcludedSchemas()) 394 if remote { 395 krs = krs.Remove(kuberesource.DefaultRemoteClusterExcludedSchemas().All()...) 396 } 397 store = crdclient.NewForSchemas(c, crdclient.Option{ 398 DomainSuffix: "cluster.local", 399 Identifier: "analysis-controller", 400 FiltersByGVK: map[config.GroupVersionKind]kubetypes.Filter{ 401 gvk.Secret: { 402 FieldSelector: secretFieldSelector, 403 }, 404 gvk.Namespace: { 405 FieldSelector: namespaceFieldSelector, 406 }, 407 gvk.Service: { 408 FieldSelector: generalSelectors, 409 }, 410 gvk.Pod: { 411 FieldSelector: generalSelectors, 412 }, 413 gvk.Deployment: { 414 FieldSelector: generalSelectors, 415 }, 416 }, 417 }, krs) 418 // RunAndWait must be called after NewForSchema so that the informers are all created and started. 419 if remote { 420 clusterID := c.ClusterID() 421 if clusterID == "" { 422 clusterID = "default" 423 } 424 sa.multiClusterStores[clusterID] = store 425 } else { 426 sa.stores = append(sa.stores, store) 427 } 428 sa.clientsToRun = append(sa.clientsToRun, c) 429 430 // Since we're using a running k8s source, try to get meshconfig and meshnetworks from the configmap. 431 if err := sa.addRunningKubeIstioConfigMapSource(c); err != nil { 432 _, err := c.Kube().CoreV1().Namespaces().Get(context.TODO(), sa.istioNamespace.String(), metav1.GetOptions{}) 433 if kerrors.IsNotFound(err) { 434 // An AnalysisMessage already show up to warn the absence of istio-system namespace, so making it debug level. 435 scope.Analysis.Debugf("%v namespace not found. Istio may not be installed in the target cluster. "+ 436 "Using default mesh configuration values for analysis", sa.istioNamespace.String()) 437 } else if err != nil { 438 scope.Analysis.Errorf("error getting mesh config from running kube source: %v", err) 439 } 440 } 441 } 442 443 // AddSource adds a source based on user supplied configstore to the current IstiodAnalyzer 444 // Assumes that the source has same or subset of resource types that this analyzer is configured with. 445 // This can be used by external users who import the analyzer as a module within their own controllers. 446 func (sa *IstiodAnalyzer) AddSource(src model.ConfigStoreController) { 447 sa.stores = append(sa.stores, src) 448 } 449 450 // AddFileKubeMeshConfig gets mesh config from the specified yaml file 451 func (sa *IstiodAnalyzer) AddFileKubeMeshConfig(file string) error { 452 by, err := os.ReadFile(file) 453 if err != nil { 454 return err 455 } 456 457 cfg, err := mesh.ApplyMeshConfigDefaults(string(by)) 458 if err != nil { 459 return err 460 } 461 462 sa.meshCfg = cfg 463 return nil 464 } 465 466 // AddFileKubeMeshNetworks gets a file meshnetworks and add it to the analyzer. 467 func (sa *IstiodAnalyzer) AddFileKubeMeshNetworks(file string) error { 468 mn, err := mesh.ReadMeshNetworks(file) 469 if err != nil { 470 return err 471 } 472 473 sa.meshNetworks = mn 474 return nil 475 } 476 477 // AddDefaultResources adds some basic dummy Istio resources, based on mesh configuration. 478 // This is useful for files-only analysis cases where we don't expect the user to be including istio system resources 479 // and don't want to generate false positives because they aren't there. 480 // Respect mesh config when deciding which default resources should be generated 481 func (sa *IstiodAnalyzer) AddDefaultResources() error { 482 var readers []ReaderSource 483 484 if sa.meshCfg.GetIngressControllerMode() != v1alpha1.MeshConfig_OFF { 485 ingressResources, err := getDefaultIstioIngressGateway(sa.istioNamespace.String(), sa.meshCfg.GetIngressService()) 486 if err != nil { 487 return err 488 } 489 readers = append(readers, ReaderSource{Reader: strings.NewReader(ingressResources), Name: "internal-ingress"}) 490 } 491 492 if len(readers) == 0 { 493 return nil 494 } 495 496 return sa.AddReaderKubeSource(readers) 497 } 498 499 func (sa *IstiodAnalyzer) RegisterEventHandler(kind config.GroupVersionKind, handler model.EventHandler) { 500 for _, store := range sa.stores { 501 store.RegisterEventHandler(kind, handler) 502 } 503 } 504 505 func (sa *IstiodAnalyzer) Schemas() collection.Schemas { 506 result := collection.NewSchemasBuilder() 507 for _, store := range sa.stores { 508 for _, schema := range store.Schemas().All() { 509 result.MustAdd(schema) 510 } 511 } 512 return result.Build() 513 } 514 515 func (sa *IstiodAnalyzer) addRunningKubeIstioConfigMapSource(client kubelib.Client) error { 516 meshConfigMap, err := client.Kube().CoreV1().ConfigMaps(string(sa.istioNamespace)).Get(context.TODO(), meshConfigMapName, metav1.GetOptions{}) 517 if err != nil { 518 return fmt.Errorf("could not read configmap %q from namespace %q: %v", meshConfigMapName, sa.istioNamespace, err) 519 } 520 521 configYaml, ok := meshConfigMap.Data[meshConfigMapKey] 522 if !ok { 523 return fmt.Errorf("missing config map key %q", meshConfigMapKey) 524 } 525 526 cfg, err := mesh.ApplyMeshConfigDefaults(configYaml) 527 if err != nil { 528 return fmt.Errorf("error parsing mesh config: %v", err) 529 } 530 531 sa.meshCfg = cfg 532 533 meshNetworksYaml, ok := meshConfigMap.Data[meshNetworksMapKey] 534 if !ok { 535 return fmt.Errorf("missing config map key %q", meshNetworksMapKey) 536 } 537 538 mn, err := mesh.ParseMeshNetworks(meshNetworksYaml) 539 if err != nil { 540 return fmt.Errorf("error parsing mesh networks: %v", err) 541 } 542 543 sa.meshNetworks = mn 544 return nil 545 } 546 547 // AddSourceForCluster adds a source based on user supplied configstore to the current IstiodAnalyzer with cluster specified. 548 // It functions like the same as AddSource, but it adds the source to the specified cluster. 549 func (sa *IstiodAnalyzer) AddSourceForCluster(src model.ConfigStoreController, clusterName cluster.ID) { 550 sa.multiClusterStores[clusterName] = src 551 } 552 553 // CollectionReporterFn is a hook function called whenever a collection is accessed through the AnalyzingDistributor's context 554 type CollectionReporterFn func(config.GroupVersionKind) 555 556 // copied from processing/snapshotter/analyzingdistributor.go 557 func filterMessages(messages diag.Messages, namespaces sets.Set[resource.Namespace], suppressions []AnalysisSuppression) diag.Messages { 558 nsNames := sets.New[string]() 559 for k := range namespaces { 560 nsNames.Insert(k.String()) 561 } 562 563 var msgs diag.Messages 564 FilterMessages: 565 for _, m := range messages { 566 // Only keep messages for resources in namespaces we want to analyze if the 567 // message doesn't have an origin (meaning we can't determine the 568 // namespace). Also kept are cluster-level resources where the namespace is 569 // the empty string. If no such limit is specified, keep them all. 570 if len(namespaces) > 0 && m.Resource != nil && m.Resource.Origin.Namespace() != "" { 571 if !nsNames.Contains(m.Resource.Origin.Namespace().String()) { 572 continue FilterMessages 573 } 574 } 575 576 // Filter out any messages on resources with suppression annotations. 577 if m.Resource != nil && m.Resource.Metadata.Annotations[annotation.GalleyAnalyzeSuppress.Name] != "" { 578 for _, code := range strings.Split(m.Resource.Metadata.Annotations[annotation.GalleyAnalyzeSuppress.Name], ",") { 579 if code == "*" || m.Type.Code() == code { 580 scope.Analysis.Debugf("Suppressing code %s on resource %s due to resource annotation", m.Type.Code(), m.Resource.Origin.FriendlyName()) 581 continue FilterMessages 582 } 583 } 584 } 585 586 // Filter out any messages that match our suppressions. 587 for _, s := range suppressions { 588 if m.Resource == nil || s.Code != m.Type.Code() { 589 continue 590 } 591 592 if !glob.Glob(s.ResourceName, m.Resource.Origin.FriendlyName()) { 593 continue 594 } 595 scope.Analysis.Debugf("Suppressing code %s on resource %s due to suppressions list", m.Type.Code(), m.Resource.Origin.FriendlyName()) 596 continue FilterMessages 597 } 598 599 msgs = append(msgs, m) 600 } 601 return msgs 602 } 603 604 // AnalysisSuppression describes a resource and analysis code to be suppressed 605 // (e.g. ignored) during analysis. Used when a particular message code is to be 606 // ignored for a specific resource. 607 type AnalysisSuppression struct { 608 // Code is the analysis code to suppress (e.g. "IST0104"). 609 Code string 610 611 // ResourceName is the name of the resource to suppress the message for. For 612 // K8s resources it has the same form as used by istioctl (e.g. 613 // "DestinationRule default.istio-system"). Note that globbing wildcards are 614 // supported (e.g. "DestinationRule *.istio-system"). 615 ResourceName string 616 } 617 618 // ReaderSource is a tuple of a io.Reader and filepath. 619 type ReaderSource struct { 620 // Name is the name of the source (commonly the path to a file, but can be "-" for sources read from stdin or "" if completely synthetic). 621 Name string 622 // Reader is the reader instance to use. 623 Reader io.Reader 624 }