1// Copyright 2018 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 is designed to listen to the config changes through the store and create a fully-resolved configuration
16// state that can be used by the rest of the runtime code.
17//
18// The main purpose of this library is to create an object-model that simplifies queries and correctness checks that
19// the client code needs to deal with. This is accomplished by making sure the config state is fully resolved, and
20// incorporating otherwise complex queries within this package.
21package config
22
23import (
24	"bytes"
25	"context"
26	"encoding/json"
27	"fmt"
28	"sync"
29
30	"github.com/gogo/protobuf/jsonpb"
31	"github.com/gogo/protobuf/proto"
32	"github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
33	"github.com/gogo/protobuf/types"
34	multierror "github.com/hashicorp/go-multierror"
35	"go.opencensus.io/stats"
36
37	"istio.io/api/mixer/adapter/model/v1beta1"
38	config "istio.io/api/policy/v1beta1"
39	"istio.io/istio/mixer/pkg/adapter"
40	"istio.io/istio/mixer/pkg/config/store"
41	"istio.io/istio/mixer/pkg/config/storetest"
42	"istio.io/istio/mixer/pkg/lang/checker"
43	"istio.io/istio/mixer/pkg/protobuf/yaml"
44	"istio.io/istio/mixer/pkg/protobuf/yaml/dynamic"
45	"istio.io/istio/mixer/pkg/runtime/config/constant"
46	"istio.io/istio/mixer/pkg/runtime/lang"
47	"istio.io/istio/mixer/pkg/runtime/monitoring"
48	"istio.io/istio/mixer/pkg/template"
49	"istio.io/pkg/attribute"
50	"istio.io/pkg/log"
51)
52
53// Ephemeral configuration state that gets updated by incoming config change events. By itself, the data contained
54// is not meaningful. BuildSnapshot must be called to create a new snapshot instance, which contains fully resolved
55// config.
56// The Ephemeral is thread safe, which mean the state can be incrementally built asynchronously, before calling
57// BuildSnapshot, using ApplyEvent.
58type Ephemeral struct {
59	// Static information
60	adapters  map[string]*adapter.Info
61	templates map[string]*template.Info
62
63	// next snapshot id
64	nextID int64
65
66	// type checkers
67	attributes attribute.AttributeDescriptorFinder
68	tcs        map[lang.LanguageRuntime]lang.TypeChecker
69
70	// The ephemeral object is used inside a webhooks validators which run as multiple nodes.
71	// Which means every ephemeral instance (associated with every isolated webhook node) needs to keep itself in sync with the
72	// store's state to do validation of incoming config stanza. The user of the ephemeral must therefore attach the
73	// ApplyEvent function to a background store store.WatchChanges callback. Therefore, we need to lock protect the entries
74	// because it can get updated either when webhook is invoked for validation or in the background via
75	// store.WatchChanges callbacks.
76	lock sync.RWMutex // protects resources below
77
78	// entries that are currently known.
79	entries map[store.Key]*store.Resource
80}
81
82// NewEphemeral returns a new Ephemeral instance.
83//
84// NOTE: initial state is computed even if there are errors in the config. Configuration that has errors
85// is reported in the returned error object, and is ignored in the snapshot creation.
86func NewEphemeral(
87	templates map[string]*template.Info,
88	adapters map[string]*adapter.Info) *Ephemeral {
89
90	e := &Ephemeral{
91		templates: templates,
92		adapters:  adapters,
93
94		nextID: 0,
95		tcs:    make(map[lang.LanguageRuntime]checker.TypeChecker),
96
97		entries: make(map[store.Key]*store.Resource),
98	}
99
100	return e
101}
102
103func (e *Ephemeral) checker(mode lang.LanguageRuntime) checker.TypeChecker {
104	if out, ok := e.tcs[mode]; ok {
105		return out
106	}
107
108	out := lang.NewTypeChecker(e.attributes, mode)
109	e.tcs[mode] = out
110	return out
111}
112
113// SetState with the supplied state map. All existing ephemeral state is overwritten.
114func (e *Ephemeral) SetState(state map[store.Key]*store.Resource) {
115	e.lock.Lock()
116	defer e.lock.Unlock()
117	e.entries = state
118}
119
120// GetEntry returns the value stored for the key in the ephemeral.
121func (e *Ephemeral) GetEntry(event *store.Event) (*store.Resource, bool) {
122	e.lock.RLock()
123	defer e.lock.RUnlock()
124	v, ok := e.entries[event.Key]
125	return v, ok
126}
127
128// ApplyEvent to the internal ephemeral state. This gets called by an external event listener to relay store change
129// events to this ephemeral config object.
130func (e *Ephemeral) ApplyEvent(events []*store.Event) {
131	e.lock.Lock()
132	defer e.lock.Unlock()
133	for _, event := range events {
134		switch event.Type {
135		case store.Update:
136			e.entries[event.Key] = event.Value
137		case store.Delete:
138			delete(e.entries, event.Key)
139		}
140	}
141}
142
143// BuildSnapshot builds a stable, fully-resolved snapshot view of the configuration.
144func (e *Ephemeral) BuildSnapshot() (*Snapshot, error) {
145	errs := &multierror.Error{}
146	id := e.nextID
147	e.nextID++
148
149	log.Debugf("Building new config.Snapshot: id='%d'", id)
150
151	// Allocate new monitoring context to use with the new snapshot.
152	monitoringCtx := context.Background()
153
154	e.lock.RLock()
155
156	attributes := e.processAttributeManifests(monitoringCtx)
157
158	shandlers := e.processStaticAdapterHandlerConfigs()
159
160	af := attribute.NewFinder(attributes)
161	e.attributes = af
162	instances, instErrs := e.processInstanceConfigs(errs)
163
164	// New dynamic configurations
165	dTemplates := e.processDynamicTemplateConfigs(monitoringCtx, errs)
166	dAdapters := e.processDynamicAdapterConfigs(monitoringCtx, dTemplates, errs)
167	dhandlers := e.processDynamicHandlerConfigs(monitoringCtx, dAdapters, errs)
168	dInstances, dInstErrs := e.processDynamicInstanceConfigs(dTemplates, errs)
169
170	rules := e.processRuleConfigs(monitoringCtx, shandlers, instances, dhandlers, dInstances, errs)
171
172	stats.Record(monitoringCtx,
173		monitoring.HandlersTotal.M(int64(len(shandlers)+len(dhandlers))),
174		monitoring.InstancesTotal.M(int64(len(instances)+len(dInstances))),
175		monitoring.RulesTotal.M(int64(len(rules))),
176		monitoring.AdapterInfosTotal.M(int64(len(dAdapters))),
177		monitoring.TemplatesTotal.M(int64(len(dTemplates))),
178		monitoring.InstanceErrs.M(instErrs+dInstErrs),
179	)
180
181	s := &Snapshot{
182		ID:                id,
183		Templates:         e.templates,
184		Adapters:          e.adapters,
185		TemplateMetadatas: dTemplates,
186		AdapterMetadatas:  dAdapters,
187		Attributes:        af,
188		HandlersStatic:    shandlers,
189		InstancesStatic:   instances,
190		Rules:             rules,
191
192		HandlersDynamic:  dhandlers,
193		InstancesDynamic: dInstances,
194
195		MonitoringContext: monitoringCtx,
196	}
197	e.lock.RUnlock()
198
199	log.Debugf("config.Snapshot creation error=%v, contents:\n%s", errs.ErrorOrNil(), s)
200	return s, errs.ErrorOrNil()
201}
202
203func (e *Ephemeral) processAttributeManifests(ctx context.Context) map[string]*config.AttributeManifest_AttributeInfo {
204	attrs := make(map[string]*config.AttributeManifest_AttributeInfo)
205	for k, obj := range e.entries {
206		if k.Kind != constant.AttributeManifestKind {
207			continue
208		}
209
210		log.Debug("Start processing attributes from changed manifest...")
211
212		cfg := obj.Spec
213		for an, at := range cfg.(*config.AttributeManifest).Attributes {
214			attrs[an] = at
215			log.Debugf("Attribute '%s': '%s'.", an, at.ValueType)
216		}
217	}
218
219	// append all the well known attribute vocabulary from the static templates.
220	//
221	// ATTRIBUTE_GENERATOR variety templates allow operators to write Attributes
222	// using the $out.<field Name> convention, where $out refers to the output object from the attribute generating adapter.
223	// The list of valid names for a given Template is available in the Template.Info.AttributeManifests object.
224	for _, info := range e.templates {
225		if info.Variety != v1beta1.TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR {
226			continue
227		}
228
229		log.Debugf("Processing attributes from template: '%s'", info.Name)
230
231		for _, v := range info.AttributeManifests {
232			for an, at := range v.Attributes {
233				attrs[an] = at
234				log.Debugf("Attribute '%s': '%s'", an, at.ValueType)
235			}
236		}
237	}
238
239	log.Debug("Completed processing attributes.")
240	stats.Record(ctx, monitoring.AttributesTotal.M(int64(len(attrs))))
241	return attrs
242}
243
244// convert converts unstructured spec into the target proto.
245func convert(spec map[string]interface{}, target proto.Message) error {
246	jsonData, err := json.Marshal(spec)
247	if err != nil {
248		return err
249	}
250	if err = jsonpb.Unmarshal(bytes.NewReader(jsonData), target); err != nil {
251		log.Warnf("unable to unmarshal: %s, %s", err.Error(), string(jsonData))
252	}
253	return err
254}
255
256func (e *Ephemeral) processStaticAdapterHandlerConfigs() map[string]*HandlerStatic {
257	handlers := make(map[string]*HandlerStatic, len(e.adapters))
258
259	for key, resource := range e.entries {
260		var info *adapter.Info
261		var found bool
262
263		if key.Kind == constant.HandlerKind {
264			log.Debugf("Static Handler: %#v (name: %s)", key, key.Name)
265			handlerProto := resource.Spec.(*config.Handler)
266			a, ok := e.adapters[handlerProto.CompiledAdapter]
267			if !ok {
268				continue
269			}
270			staticConfig := &HandlerStatic{
271				// example: stdio.istio-system
272				Name:    key.Name + "." + key.Namespace,
273				Adapter: a,
274				Params:  a.DefaultConfig,
275			}
276			if handlerProto.Params != nil {
277				c := proto.Clone(staticConfig.Adapter.DefaultConfig)
278				if handlerProto.GetParams() != nil {
279					dict, err := toDictionary(handlerProto.Params)
280					if err != nil {
281						log.Warnf("could not convert handler params; using default config: %v", err)
282					} else if err := convert(dict, c); err != nil {
283						log.Warnf("could not convert handler params; using default config: %v", err)
284					}
285				}
286				staticConfig.Params = c
287			}
288			handlers[key.String()] = staticConfig
289			continue
290		}
291
292		if info, found = e.adapters[key.Kind]; !found {
293			// This config resource is not for an adapter (or at least not for one that Mixer is currently aware of).
294			continue
295		}
296
297		adapterName := key.String()
298
299		log.Debugf("Processing incoming handler config: name='%s'\n%s", adapterName, resource.Spec)
300
301		cfg := &HandlerStatic{
302			Name:    adapterName,
303			Adapter: info,
304			Params:  resource.Spec,
305		}
306
307		handlers[cfg.Name] = cfg
308	}
309
310	return handlers
311}
312
313func getCanonicalRef(n, kind, ns string, lookup func(string) interface{}) (interface{}, string) {
314	name, altName := canonicalize(n, kind, ns)
315	v := lookup(name)
316	if v != nil {
317		return v, name
318	}
319
320	return lookup(altName), altName
321}
322
323func (e *Ephemeral) processDynamicHandlerConfigs(ctx context.Context, adapters map[string]*Adapter, errs *multierror.Error) map[string]*HandlerDynamic {
324	handlers := make(map[string]*HandlerDynamic, len(e.adapters))
325	var validationErrs int64
326
327	for key, resource := range e.entries {
328		if key.Kind != constant.HandlerKind {
329			continue
330		}
331
332		handlerName := key.String()
333		log.Debugf("Processing incoming handler config: name='%s'\n%s", handlerName, resource.Spec)
334
335		hdl := resource.Spec.(*config.Handler)
336		if len(hdl.CompiledAdapter) > 0 {
337			continue // this will have already been added in processStaticHandlerConfigs
338		}
339		adpt, _ := getCanonicalRef(hdl.Adapter, constant.AdapterKind, key.Namespace, func(n string) interface{} {
340			if a, ok := adapters[n]; ok {
341				return a
342			}
343			return nil
344		})
345
346		if adpt == nil {
347			validationErrs++
348			appendErr(errs, fmt.Sprintf("handler='%s'.adapter", handlerName), "adapter '%s' not found", hdl.Adapter)
349			continue
350		}
351		adapter := adpt.(*Adapter)
352
353		var adapterCfg *types.Any
354		if len(adapter.ConfigDescSet.File) != 0 {
355			// validate if the param is valid
356			bytes, err := validateEncodeBytes(hdl.Params, adapter.ConfigDescSet, getParamsMsgFullName(adapter.PackageName))
357			if err != nil {
358				validationErrs++
359				appendErr(errs, fmt.Sprintf("handler='%s'.params", handlerName), err.Error())
360				continue
361			}
362			typeFQN := adapter.PackageName + ".Params"
363			adapterCfg = asAny(typeFQN, bytes)
364		}
365
366		cfg := &HandlerDynamic{
367			Name:          handlerName,
368			Adapter:       adapter,
369			Connection:    hdl.Connection,
370			AdapterConfig: adapterCfg,
371		}
372
373		handlers[cfg.Name] = cfg
374	}
375
376	stats.Record(ctx, monitoring.HandlerValidationErrors.M(validationErrs))
377
378	return handlers
379}
380
381const googleApis = "type.googleapis.com/"
382
383func asAny(msgFQN string, bytes []byte) *types.Any {
384	return &types.Any{
385		TypeUrl: googleApis + msgFQN,
386		Value:   bytes,
387	}
388}
389
390func (e *Ephemeral) processDynamicInstanceConfigs(templates map[string]*Template, errs *multierror.Error) (map[string]*InstanceDynamic, int64) {
391	instances := make(map[string]*InstanceDynamic, len(e.templates))
392	var instErrs int64
393
394	for key, resource := range e.entries {
395		if key.Kind != constant.InstanceKind {
396			continue
397		}
398
399		inst := resource.Spec.(*config.Instance)
400
401		// should be processed as static instance
402		if inst.CompiledTemplate != "" {
403			continue
404		}
405
406		instanceName := key.String()
407		log.Debugf("Processing incoming instance config: name='%s'\n%s", instanceName, resource.Spec)
408
409		tmpl, _ := getCanonicalRef(inst.Template, constant.TemplateKind, key.Namespace, func(n string) interface{} {
410			if a, ok := templates[n]; ok {
411				return a
412			}
413			return nil
414		})
415
416		if tmpl == nil {
417			instErrs++
418			appendErr(errs, fmt.Sprintf("instance='%s'.template", instanceName), "template '%s' not found", inst.Template)
419			continue
420		}
421
422		template := tmpl.(*Template)
423		// validate if the param is valid
424		mode := lang.GetLanguageRuntime(resource.Metadata.Annotations)
425		compiler := lang.NewBuilder(e.attributes, mode)
426		resolver := yaml.NewResolver(template.FileDescSet)
427		b := dynamic.NewEncoderBuilder(
428			resolver,
429			compiler,
430			false)
431		var enc dynamic.Encoder
432		var params map[string]interface{}
433		var err error
434		if inst.Params != nil {
435			if params, err = toDictionary(inst.Params); err != nil {
436				instErrs++
437				appendErr(errs, fmt.Sprintf("instance='%s'.params", instanceName), "invalid params block.")
438				continue
439			}
440
441			// name field is not provided by instance config author, instead it is added by Mixer into the request
442			// object that is passed to the adapter.
443			params["name"] = fmt.Sprintf("\"%s\"", instanceName)
444			enc, err = b.Build(getTemplatesMsgFullName(template.PackageName), params)
445			if err != nil {
446				instErrs++
447				appendErr(errs, fmt.Sprintf("instance='%s'.params", instanceName),
448					"config does not conform to schema of template '%s': %v", inst.Template, err.Error())
449				continue
450			}
451		}
452
453		cfg := &InstanceDynamic{
454			Name:              instanceName,
455			Template:          template,
456			Encoder:           enc,
457			Params:            params,
458			AttributeBindings: inst.AttributeBindings,
459			Language:          mode,
460		}
461
462		instances[cfg.Name] = cfg
463	}
464
465	return instances, instErrs
466}
467
468func getTemplatesMsgFullName(pkgName string) string {
469	return "." + pkgName + ".InstanceMsg"
470}
471
472func getParamsMsgFullName(pkgName string) string {
473	return "." + pkgName + ".Params"
474}
475
476func validateEncodeBytes(params *types.Struct, fds *descriptor.FileDescriptorSet, msgName string) ([]byte, error) {
477	if params == nil {
478		return []byte{}, nil
479	}
480	d, err := toDictionary(params)
481	if err != nil {
482		return nil, fmt.Errorf("error converting parameters to dictionary: %v", err)
483	}
484	return yaml.NewEncoder(fds).EncodeBytes(d, msgName, false)
485}
486
487func (e *Ephemeral) processInstanceConfigs(errs *multierror.Error) (map[string]*InstanceStatic, int64) {
488	instances := make(map[string]*InstanceStatic, len(e.templates))
489	var instErrs int64
490
491	for key, resource := range e.entries {
492		var info *template.Info
493		var found bool
494
495		var params proto.Message
496		instanceName := key.String()
497
498		if key.Kind == constant.InstanceKind {
499			inst := resource.Spec.(*config.Instance)
500			if inst.CompiledTemplate == "" {
501				continue
502			}
503			info, found = e.templates[inst.CompiledTemplate]
504			if !found {
505				instErrs++
506				appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), "missing compiled template")
507				continue
508			}
509
510			// cast from struct to template specific proto
511			params = proto.Clone(info.CtrCfg)
512			buf := &bytes.Buffer{}
513
514			if inst.Params == nil {
515				inst.Params = &types.Struct{Fields: make(map[string]*types.Value)}
516			}
517			// populate attribute bindings
518			if len(inst.AttributeBindings) > 0 {
519				bindings := &types.Struct{Fields: make(map[string]*types.Value)}
520				for k, v := range inst.AttributeBindings {
521					bindings.Fields[k] = &types.Value{Kind: &types.Value_StringValue{StringValue: v}}
522				}
523				inst.Params.Fields["attribute_bindings"] = &types.Value{
524					Kind: &types.Value_StructValue{StructValue: bindings},
525				}
526			}
527			if err := (&jsonpb.Marshaler{}).Marshal(buf, inst.Params); err != nil {
528				instErrs++
529				appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error())
530				continue
531			}
532			if err := (&jsonpb.Unmarshaler{AllowUnknownFields: false}).Unmarshal(buf, params); err != nil {
533				instErrs++
534				appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error())
535				continue
536			}
537		} else {
538			info, found = e.templates[key.Kind]
539			if !found {
540				// This config resource is not for an instance (or at least not for one that Mixer is currently aware of).
541				continue
542			}
543			params = resource.Spec
544		}
545
546		mode := lang.GetLanguageRuntime(resource.Metadata.Annotations)
547
548		log.Debugf("Processing incoming instance config: name='%s'\n%s", instanceName, params)
549		inferredType, err := info.InferType(params, func(s string) (config.ValueType, error) {
550			return e.checker(mode).EvalType(s)
551		})
552
553		if err != nil {
554			instErrs++
555			appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error())
556			continue
557		}
558		cfg := &InstanceStatic{
559			Name:         instanceName,
560			Template:     info,
561			Params:       params,
562			InferredType: inferredType,
563			Language:     mode,
564		}
565
566		instances[cfg.Name] = cfg
567	}
568
569	return instances, instErrs
570}
571
572func (e *Ephemeral) processDynamicAdapterConfigs(ctx context.Context, availableTmpls map[string]*Template, errs *multierror.Error) map[string]*Adapter {
573	result := map[string]*Adapter{}
574	log.Debug("Begin processing adapter info configurations.")
575	var adapterErrs int64
576	for adapterInfoKey, resource := range e.entries {
577		if adapterInfoKey.Kind != constant.AdapterKind {
578			continue
579		}
580
581		adapterName := adapterInfoKey.String()
582
583		cfg := resource.Spec.(*v1beta1.Info)
584
585		log.Debugf("Processing incoming adapter info: name='%s'\n%v", adapterName, cfg)
586
587		fds, desc, err := GetAdapterCfgDescriptor(cfg.Config)
588		if err != nil {
589			adapterErrs++
590			appendErr(errs, fmt.Sprintf("adapter='%s'", adapterName), "unable to parse adapter configuration: %v", err)
591			continue
592		}
593		supportedTmpls := make([]*Template, 0, len(cfg.Templates))
594		for _, tmplN := range cfg.Templates {
595
596			template, tmplFullName := getCanonicalRef(tmplN, constant.TemplateKind, adapterInfoKey.Namespace, func(n string) interface{} {
597				if a, ok := availableTmpls[n]; ok {
598					return a
599				}
600				return nil
601			})
602			if template == nil {
603				adapterErrs++
604				appendErr(errs, fmt.Sprintf("adapter='%s'", adapterName), "unable to find template '%s'", tmplN)
605				continue
606			}
607			supportedTmpls = append(supportedTmpls, availableTmpls[tmplFullName])
608		}
609		if len(cfg.Templates) == len(supportedTmpls) {
610			// only record adapter if all templates are valid
611			result[adapterName] = &Adapter{
612				Name:               adapterName,
613				ConfigDescSet:      fds,
614				PackageName:        desc.GetPackage(),
615				SupportedTemplates: supportedTmpls,
616				SessionBased:       cfg.SessionBased,
617				Description:        cfg.Description,
618			}
619		}
620	}
621
622	stats.Record(ctx, monitoring.AdapterErrs.M(adapterErrs))
623	return result
624}
625
626func assertType(tc lang.TypeChecker, expression string, expectedType config.ValueType) error {
627	if t, err := tc.EvalType(expression); err != nil {
628		return err
629	} else if t != expectedType {
630		return fmt.Errorf("expression '%s' evaluated to type %v, expected type %v", expression, t, expectedType)
631	}
632	return nil
633}
634
635func (e *Ephemeral) processRuleConfigs(
636	ctx context.Context,
637	sHandlers map[string]*HandlerStatic,
638	sInstances map[string]*InstanceStatic,
639	dHandlers map[string]*HandlerDynamic,
640	dInstances map[string]*InstanceDynamic,
641	errs *multierror.Error) []*Rule {
642
643	log.Debug("Begin processing rule configurations.")
644	var ruleErrs int64
645	var rules []*Rule
646
647	for ruleKey, resource := range e.entries {
648		if ruleKey.Kind != constant.RulesKind {
649			continue
650		}
651
652		ruleName := ruleKey.String()
653
654		cfg := resource.Spec.(*config.Rule)
655		mode := lang.GetLanguageRuntime(resource.Metadata.Annotations)
656
657		log.Debugf("Processing incoming rule: name='%s'\n%s", ruleName, cfg)
658
659		if cfg.Match != "" {
660			if err := assertType(e.checker(mode), cfg.Match, config.BOOL); err != nil {
661				ruleErrs++
662				appendErr(errs, fmt.Sprintf("rule='%s'.Match", ruleName), err.Error())
663			}
664		}
665
666		// extract the set of actions from the rule, and the handlers they reference.
667		// A rule can have both static and dynamic actions.
668
669		actionsStat := make([]*ActionStatic, 0, len(cfg.Actions))
670		actionsDynamic := make([]*ActionDynamic, 0, len(cfg.Actions))
671		for i, a := range cfg.Actions {
672			log.Debugf("Processing action: %s[%d]", ruleName, i)
673			var processStaticHandler bool
674			var processDynamicHandler bool
675			var sahandler *HandlerStatic
676			var dahandler *HandlerDynamic
677			hdl, handlerName := getCanonicalRef(a.Handler, constant.HandlerKind, ruleKey.Namespace, func(n string) interface{} {
678				if a, ok := sHandlers[n]; ok {
679					return a
680				}
681				return nil
682			})
683			if hdl != nil {
684				sahandler = hdl.(*HandlerStatic)
685				processStaticHandler = true
686			} else {
687				hdl, handlerName = getCanonicalRef(a.Handler, constant.HandlerKind, ruleKey.Namespace, func(n string) interface{} {
688					if a, ok := dHandlers[n]; ok {
689						return a
690					}
691					return nil
692				})
693
694				if hdl != nil {
695					dahandler = hdl.(*HandlerDynamic)
696					processDynamicHandler = true
697				}
698			}
699
700			if !processStaticHandler && !processDynamicHandler {
701				ruleErrs++
702				appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "Handler not found: handler='%s'", a.Handler)
703				continue
704			}
705
706			if processStaticHandler {
707				// Keep track of unique instances, to avoid using the same instance multiple times within the same
708				// action
709				uniqueInstances := make(map[string]bool, len(a.Instances))
710
711				actionInstances := make([]*InstanceStatic, 0, len(a.Instances))
712				for _, instanceNameRef := range a.Instances {
713					inst, instName := getCanonicalRef(instanceNameRef, constant.InstanceKind, ruleKey.Namespace, func(n string) interface{} {
714						if a, ok := sInstances[n]; ok {
715							return a
716						}
717						return nil
718					})
719
720					if inst == nil {
721						ruleErrs++
722						appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "Instance not found: instance='%s'", instanceNameRef)
723						continue
724					}
725
726					instance := inst.(*InstanceStatic)
727
728					if _, ok := uniqueInstances[instName]; ok {
729						ruleErrs++
730						appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i),
731							"action specified the same instance multiple times: instance='%s',", instName)
732						continue
733					}
734					uniqueInstances[instName] = true
735
736					if !contains(sahandler.Adapter.SupportedTemplates, instance.Template.Name) {
737						ruleErrs++
738						appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i),
739							"instance '%s' is of template '%s' which is not supported by handler '%s'",
740							instName, instance.Template.Name, handlerName)
741						continue
742					}
743
744					actionInstances = append(actionInstances, instance)
745				}
746
747				// If there are no valid instances found for this action, then elide the action.
748				if len(actionInstances) == 0 {
749					ruleErrs++
750					appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "No valid instances found")
751					continue
752				}
753
754				action := &ActionStatic{
755					Handler:   sahandler,
756					Instances: actionInstances,
757					Name:      a.Name,
758				}
759
760				actionsStat = append(actionsStat, action)
761			} else {
762				// Keep track of unique instances, to avoid using the same instance multiple times within the same
763				// action
764				uniqueInstances := make(map[string]bool, len(a.Instances))
765
766				actionInstances := make([]*InstanceDynamic, 0, len(a.Instances))
767				for _, instanceNameRef := range a.Instances {
768					inst, instName := getCanonicalRef(instanceNameRef, constant.InstanceKind, ruleKey.Namespace, func(n string) interface{} {
769						if a, ok := dInstances[n]; ok {
770							return a
771						}
772						return nil
773					})
774
775					if inst == nil {
776						ruleErrs++
777						appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "Instance not found: instance='%s'", instanceNameRef)
778						continue
779					}
780
781					instance := inst.(*InstanceDynamic)
782					if _, ok := uniqueInstances[instName]; ok {
783						ruleErrs++
784						appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i),
785							"action specified the same instance multiple times: instance='%s',", instName)
786						continue
787					}
788					uniqueInstances[instName] = true
789
790					found := false
791					for _, supTmpl := range dahandler.Adapter.SupportedTemplates {
792						if supTmpl.Name == instance.Template.Name {
793							found = true
794							break
795						}
796					}
797
798					if !found {
799						ruleErrs++
800						appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i),
801							"instance '%s' is of template '%s' which is not supported by handler '%s'",
802							instName, instance.Template.Name, handlerName)
803						continue
804					}
805
806					actionInstances = append(actionInstances, instance)
807				}
808
809				// If there are no valid instances found for this action, then elide the action.
810				if len(actionInstances) == 0 {
811					ruleErrs++
812					appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "No valid instances found")
813					continue
814				}
815
816				action := &ActionDynamic{
817					Handler:   dahandler,
818					Instances: actionInstances,
819					Name:      a.Name,
820				}
821
822				actionsDynamic = append(actionsDynamic, action)
823			}
824		}
825
826		// If there are no valid actions found for this rule, then elide the rule.
827		if len(actionsStat) == 0 && len(actionsDynamic) == 0 &&
828			len(cfg.RequestHeaderOperations) == 0 && len(cfg.ResponseHeaderOperations) == 0 {
829			ruleErrs++
830			appendErr(errs, fmt.Sprintf("rule=%s", ruleName), "No valid actions found in rule")
831			continue
832		}
833
834		rule := &Rule{
835			Name:                     ruleName,
836			Namespace:                ruleKey.Namespace,
837			ActionsStatic:            actionsStat,
838			ActionsDynamic:           actionsDynamic,
839			Match:                    cfg.Match,
840			RequestHeaderOperations:  cfg.RequestHeaderOperations,
841			ResponseHeaderOperations: cfg.ResponseHeaderOperations,
842			Language:                 mode,
843		}
844
845		rules = append(rules, rule)
846	}
847
848	stats.Record(ctx, monitoring.RuleErrs.M(ruleErrs))
849
850	return rules
851}
852
853func contains(strs []string, w string) bool {
854	for _, v := range strs {
855		if v == w {
856			return true
857		}
858	}
859	return false
860}
861
862func (e *Ephemeral) processDynamicTemplateConfigs(ctx context.Context, errs *multierror.Error) map[string]*Template {
863	result := map[string]*Template{}
864	log.Debug("Begin processing templates.")
865	var templateErrs int64
866	for templateKey, resource := range e.entries {
867		if templateKey.Kind != constant.TemplateKind {
868			continue
869		}
870
871		templateName := templateKey.String()
872		cfg := resource.Spec.(*v1beta1.Template)
873		log.Debugf("Processing incoming template: name='%s'\n%v", templateName, cfg)
874
875		fds, desc, name, variety, err := GetTmplDescriptor(cfg.Descriptor_)
876		if err != nil {
877			templateErrs++
878			appendErr(errs, fmt.Sprintf("template='%s'", templateName), "unable to parse descriptor: %v", err)
879			continue
880		}
881
882		result[templateName] = &Template{
883			Name:                       templateName,
884			InternalPackageDerivedName: name,
885			FileDescSet:                fds,
886			PackageName:                desc.GetPackage(),
887			Variety:                    variety,
888		}
889
890		if variety == v1beta1.TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR || variety == v1beta1.TEMPLATE_VARIETY_CHECK_WITH_OUTPUT {
891			resolver := yaml.NewResolver(fds)
892			msgName := "." + desc.GetPackage() + ".OutputMsg"
893			// OutputMsg is a fixed generated name for the output template
894			desc := resolver.ResolveMessage(msgName)
895			if desc == nil {
896				continue
897			}
898
899			// We only support a single level of nesting in output templates.
900			attributes := make(map[string]*config.AttributeManifest_AttributeInfo)
901			for _, field := range desc.GetField() {
902				attributes["output."+field.GetName()] = &config.AttributeManifest_AttributeInfo{
903					ValueType: yaml.DecodeType(resolver, field),
904				}
905			}
906
907			result[templateName].AttributeManifest = attributes
908		}
909	}
910	stats.Record(ctx, monitoring.TemplateErrs.M(templateErrs))
911	return result
912}
913
914func appendErr(errs *multierror.Error, field string, format string, a ...interface{}) {
915	err := fmt.Errorf(format, a...)
916	log.Debug(err.Error())
917	_ = multierror.Append(errs, adapter.ConfigError{Field: field, Underlying: err})
918}
919
920// GetSnapshotForTest creates a config.Snapshot for testing purposes, based on the supplied configuration.
921func GetSnapshotForTest(templates map[string]*template.Info, adapters map[string]*adapter.Info, serviceConfig string, globalConfig string) (*Snapshot, error) {
922	store, _ := storetest.SetupStoreForTest(serviceConfig, globalConfig)
923
924	_ = store.Init(KindMap(adapters, templates))
925
926	data := store.List()
927
928	// NewEphemeral tries to build a snapshot with empty entries therefore it never fails; Ignoring the error.
929	e := NewEphemeral(templates, adapters)
930
931	e.SetState(data)
932
933	store.Stop()
934
935	return e.BuildSnapshot()
936}
937