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  }