1// Copyright 2017 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/*
16Package component defines an in-memory representation of IstioOperator.<Feature>.<Component>. It provides functions
17for manipulating the component and rendering a manifest from it.
18See ../README.md for an architecture overview.
19*/
20package component
21
22import (
23	"fmt"
24
25	"github.com/ghodss/yaml"
26
27	"istio.io/api/operator/v1alpha1"
28	"istio.io/istio/operator/pkg/helm"
29	"istio.io/istio/operator/pkg/name"
30	"istio.io/istio/operator/pkg/patch"
31	"istio.io/istio/operator/pkg/tpath"
32	"istio.io/istio/operator/pkg/translate"
33	"istio.io/pkg/log"
34)
35
36const (
37	// addonsChartDirName is the default subdir for all addon charts.
38	addonsChartDirName = "addons"
39	// String to emit for any component which is disabled.
40	componentDisabledStr = "component is disabled."
41	yamlCommentStr       = "#"
42
43	// devDbg generates lots of output useful in development.
44	devDbg = false
45)
46
47var (
48	scope = log.RegisterScope("installer", "installer", 0)
49)
50
51// Options defines options for a component.
52type Options struct {
53	// installSpec is the global IstioOperatorSpec.
54	InstallSpec *v1alpha1.IstioOperatorSpec
55	// translator is the translator for this component.
56	Translator *translate.Translator
57	// Namespace is the namespace for this component.
58	Namespace string
59}
60
61// IstioComponent defines the interface for a component.
62type IstioComponent interface {
63	// ComponentName returns the name of the component.
64	ComponentName() name.ComponentName
65	// ResourceName returns the name of the resources of the component.
66	ResourceName() string
67	// Namespace returns the namespace for the component.
68	Namespace() string
69	// Enabled reports whether the component is enabled.
70	Enabled() bool
71	// Run starts the component. Must me called before the component is used.
72	Run() error
73	// RenderManifest returns a string with the rendered manifest for the component.
74	RenderManifest() (string, error)
75}
76
77// CommonComponentFields is a struct common to all components.
78type CommonComponentFields struct {
79	*Options
80	ComponentName name.ComponentName
81	// addonName is the name of the addon component.
82	addonName string
83	// resourceName is the name of all resources for this component.
84	ResourceName string
85	// index is the index of the component (only used for components with multiple instances like gateways).
86	index int
87	// componentSpec for the actual component e.g. GatewaySpec, ComponentSpec.
88	componentSpec interface{}
89	// started reports whether the component is in initialized and running.
90	started  bool
91	renderer helm.TemplateRenderer
92}
93
94// NewCoreComponent creates a new IstioComponent with the given componentName and options.
95func NewCoreComponent(cn name.ComponentName, opts *Options) IstioComponent {
96	var component IstioComponent
97	switch cn {
98	case name.IstioBaseComponentName:
99		component = NewCRDComponent(opts)
100	case name.PilotComponentName:
101		component = NewPilotComponent(opts)
102	case name.PolicyComponentName:
103		component = NewPolicyComponent(opts)
104	case name.TelemetryComponentName:
105		component = NewTelemetryComponent(opts)
106	case name.CNIComponentName:
107		component = NewCNIComponent(opts)
108	case name.IstiodRemoteComponentName:
109		component = NewIstiodRemoteComponent(opts)
110	default:
111		panic("Unknown component componentName: " + string(cn))
112	}
113	return component
114}
115
116// BaseComponent is the base component.
117type BaseComponent struct {
118	*CommonComponentFields
119}
120
121// NewCRDComponent creates a new BaseComponent and returns a pointer to it.
122func NewCRDComponent(opts *Options) *BaseComponent {
123	return &BaseComponent{
124		&CommonComponentFields{
125			Options:       opts,
126			ComponentName: name.IstioBaseComponentName,
127		},
128	}
129}
130
131// Run implements the IstioComponent interface.
132func (c *BaseComponent) Run() error {
133	return runComponent(c.CommonComponentFields)
134}
135
136// RenderManifest implements the IstioComponent interface.
137func (c *BaseComponent) RenderManifest() (string, error) {
138	return renderManifest(c, c.CommonComponentFields)
139}
140
141// ComponentName implements the IstioComponent interface.
142func (c *BaseComponent) ComponentName() name.ComponentName {
143	return c.CommonComponentFields.ComponentName
144}
145
146// ResourceName implements the IstioComponent interface.
147func (c *BaseComponent) ResourceName() string {
148	return c.CommonComponentFields.ResourceName
149}
150
151// Namespace implements the IstioComponent interface.
152func (c *BaseComponent) Namespace() string {
153	return c.CommonComponentFields.Namespace
154}
155
156// Enabled implements the IstioComponent interface.
157func (c *BaseComponent) Enabled() bool {
158	return isCoreComponentEnabled(c.CommonComponentFields)
159}
160
161// PilotComponent is the pilot component.
162type PilotComponent struct {
163	*CommonComponentFields
164}
165
166// NewPilotComponent creates a new PilotComponent and returns a pointer to it.
167func NewPilotComponent(opts *Options) *PilotComponent {
168	cn := name.PilotComponentName
169	return &PilotComponent{
170		&CommonComponentFields{
171			Options:       opts,
172			ComponentName: cn,
173			ResourceName:  opts.Translator.ComponentMaps[cn].ResourceName,
174		},
175	}
176}
177
178// Run implements the IstioComponent interface.
179func (c *PilotComponent) Run() error {
180	return runComponent(c.CommonComponentFields)
181}
182
183// RenderManifest implements the IstioComponent interface.
184func (c *PilotComponent) RenderManifest() (string, error) {
185	return renderManifest(c, c.CommonComponentFields)
186}
187
188// ComponentName implements the IstioComponent interface.
189func (c *PilotComponent) ComponentName() name.ComponentName {
190	return c.CommonComponentFields.ComponentName
191}
192
193// ResourceName implements the IstioComponent interface.
194func (c *PilotComponent) ResourceName() string {
195	return c.CommonComponentFields.ResourceName
196}
197
198// Namespace implements the IstioComponent interface.
199func (c *PilotComponent) Namespace() string {
200	return c.CommonComponentFields.Namespace
201}
202
203// Enabled implements the IstioComponent interface.
204func (c *PilotComponent) Enabled() bool {
205	return isCoreComponentEnabled(c.CommonComponentFields)
206}
207
208// PolicyComponent is the pilot component.
209type PolicyComponent struct {
210	*CommonComponentFields
211}
212
213// NewPolicyComponent creates a new PilotComponent and returns a pointer to it.
214func NewPolicyComponent(opts *Options) *PolicyComponent {
215	cn := name.PolicyComponentName
216	return &PolicyComponent{
217		&CommonComponentFields{
218			Options:       opts,
219			ComponentName: cn,
220		},
221	}
222}
223
224// Run implements the IstioComponent interface.
225func (c *PolicyComponent) Run() error {
226	return runComponent(c.CommonComponentFields)
227}
228
229// RenderManifest implements the IstioComponent interface.
230func (c *PolicyComponent) RenderManifest() (string, error) {
231	return renderManifest(c, c.CommonComponentFields)
232}
233
234// ComponentName implements the IstioComponent interface.
235func (c *PolicyComponent) ComponentName() name.ComponentName {
236	return c.CommonComponentFields.ComponentName
237}
238
239// ResourceName implements the IstioComponent interface.
240func (c *PolicyComponent) ResourceName() string {
241	return c.CommonComponentFields.ResourceName
242}
243
244// Namespace implements the IstioComponent interface.
245func (c *PolicyComponent) Namespace() string {
246	return c.CommonComponentFields.Namespace
247}
248
249// Enabled implements the IstioComponent interface.
250func (c *PolicyComponent) Enabled() bool {
251	return isCoreComponentEnabled(c.CommonComponentFields)
252}
253
254// TelemetryComponent is the pilot component.
255type TelemetryComponent struct {
256	*CommonComponentFields
257}
258
259// NewTelemetryComponent creates a new PilotComponent and returns a pointer to it.
260func NewTelemetryComponent(opts *Options) *TelemetryComponent {
261	cn := name.TelemetryComponentName
262	return &TelemetryComponent{
263		&CommonComponentFields{
264			Options:       opts,
265			ComponentName: cn,
266		},
267	}
268}
269
270// Run implements the IstioComponent interface.
271func (c *TelemetryComponent) Run() error {
272	return runComponent(c.CommonComponentFields)
273}
274
275// RenderManifest implements the IstioComponent interface.
276func (c *TelemetryComponent) RenderManifest() (string, error) {
277	return renderManifest(c, c.CommonComponentFields)
278}
279
280// ComponentName implements the IstioComponent interface.
281func (c *TelemetryComponent) ComponentName() name.ComponentName {
282	return c.CommonComponentFields.ComponentName
283}
284
285// ResourceName implements the IstioComponent interface.
286func (c *TelemetryComponent) ResourceName() string {
287	return c.CommonComponentFields.ResourceName
288}
289
290// Namespace implements the IstioComponent interface.
291func (c *TelemetryComponent) Namespace() string {
292	return c.CommonComponentFields.Namespace
293}
294
295// Enabled implements the IstioComponent interface.
296func (c *TelemetryComponent) Enabled() bool {
297	return isCoreComponentEnabled(c.CommonComponentFields)
298}
299
300// CNIComponent is the istio cni component.
301type CNIComponent struct {
302	*CommonComponentFields
303}
304
305// NewCNIComponent creates a new NewCNIComponent and returns a pointer to it.
306func NewCNIComponent(opts *Options) *CNIComponent {
307	cn := name.CNIComponentName
308	return &CNIComponent{
309		&CommonComponentFields{
310			Options:       opts,
311			ComponentName: cn,
312		},
313	}
314}
315
316// Run implements the IstioComponent interface.
317func (c *CNIComponent) Run() error {
318	return runComponent(c.CommonComponentFields)
319}
320
321// RenderManifest implements the IstioComponent interface.
322func (c *CNIComponent) RenderManifest() (string, error) {
323	return renderManifest(c, c.CommonComponentFields)
324}
325
326// ComponentName implements the IstioComponent interface.
327func (c *CNIComponent) ComponentName() name.ComponentName {
328	return c.CommonComponentFields.ComponentName
329}
330
331// ResourceName implements the IstioComponent interface.
332func (c *CNIComponent) ResourceName() string {
333	return c.CommonComponentFields.ResourceName
334}
335
336// Namespace implements the IstioComponent interface.
337func (c *CNIComponent) Namespace() string {
338	return c.CommonComponentFields.Namespace
339}
340
341// Enabled implements the IstioComponent interface.
342func (c *CNIComponent) Enabled() bool {
343	return isCoreComponentEnabled(c.CommonComponentFields)
344}
345
346// IstiodRemoteComponent is the istiod remote component.
347type IstiodRemoteComponent struct {
348	*CommonComponentFields
349}
350
351// NewIstiodRemoteComponent creates a new NewIstiodRemoteComponent and returns a pointer to it.
352func NewIstiodRemoteComponent(opts *Options) *IstiodRemoteComponent {
353	cn := name.IstiodRemoteComponentName
354	return &IstiodRemoteComponent{
355		&CommonComponentFields{
356			Options:       opts,
357			ComponentName: cn,
358		},
359	}
360}
361
362// Run implements the IstioComponent interface.
363func (c *IstiodRemoteComponent) Run() error {
364	return runComponent(c.CommonComponentFields)
365}
366
367// RenderManifest implements the IstioComponent interface.
368func (c *IstiodRemoteComponent) RenderManifest() (string, error) {
369	return renderManifest(c, c.CommonComponentFields)
370}
371
372// ComponentName implements the IstioComponent interface.
373func (c *IstiodRemoteComponent) ComponentName() name.ComponentName {
374	return c.CommonComponentFields.ComponentName
375}
376
377// ResourceName implements the IstioComponent interface.
378func (c *IstiodRemoteComponent) ResourceName() string {
379	return c.CommonComponentFields.ResourceName
380}
381
382// Namespace implements the IstioComponent interface.
383func (c *IstiodRemoteComponent) Namespace() string {
384	return c.CommonComponentFields.Namespace
385}
386
387// Enabled implements the IstioComponent interface.
388func (c *IstiodRemoteComponent) Enabled() bool {
389	return isCoreComponentEnabled(c.CommonComponentFields)
390}
391
392// IngressComponent is the ingress gateway component.
393type IngressComponent struct {
394	*CommonComponentFields
395}
396
397// NewIngressComponent creates a new IngressComponent and returns a pointer to it.
398func NewIngressComponent(resourceName string, index int, spec *v1alpha1.GatewaySpec, opts *Options) *IngressComponent {
399	cn := name.IngressComponentName
400	return &IngressComponent{
401		CommonComponentFields: &CommonComponentFields{
402			Options:       opts,
403			ComponentName: cn,
404			ResourceName:  resourceName,
405			index:         index,
406			componentSpec: spec,
407		},
408	}
409}
410
411// Run implements the IstioComponent interface.
412func (c *IngressComponent) Run() error {
413	return runComponent(c.CommonComponentFields)
414}
415
416// RenderManifest implements the IstioComponent interface.
417func (c *IngressComponent) RenderManifest() (string, error) {
418	return renderManifest(c, c.CommonComponentFields)
419}
420
421// ComponentName implements the IstioComponent interface.
422func (c *IngressComponent) ComponentName() name.ComponentName {
423	return c.CommonComponentFields.ComponentName
424}
425
426// ResourceName implements the IstioComponent interface.
427func (c *IngressComponent) ResourceName() string {
428	return c.CommonComponentFields.ResourceName
429}
430
431// Namespace implements the IstioComponent interface.
432func (c *IngressComponent) Namespace() string {
433	return c.CommonComponentFields.Namespace
434}
435
436// Enabled implements the IstioComponent interface.
437func (c *IngressComponent) Enabled() bool {
438	// type assert is guaranteed to work in this context.
439	return boolValue(c.componentSpec.(*v1alpha1.GatewaySpec).Enabled)
440}
441
442// EgressComponent is the egress gateway component.
443type EgressComponent struct {
444	*CommonComponentFields
445}
446
447// NewEgressComponent creates a new IngressComponent and returns a pointer to it.
448func NewEgressComponent(resourceName string, index int, spec *v1alpha1.GatewaySpec, opts *Options) *EgressComponent {
449	cn := name.EgressComponentName
450	return &EgressComponent{
451		CommonComponentFields: &CommonComponentFields{
452			Options:       opts,
453			ComponentName: cn,
454			index:         index,
455			componentSpec: spec,
456			ResourceName:  resourceName,
457		},
458	}
459}
460
461// Run implements the IstioComponent interface.
462func (c *EgressComponent) Run() error {
463	return runComponent(c.CommonComponentFields)
464}
465
466// RenderManifest implements the IstioComponent interface.
467func (c *EgressComponent) RenderManifest() (string, error) {
468	return renderManifest(c, c.CommonComponentFields)
469}
470
471// ComponentName implements the IstioComponent interface.
472func (c *EgressComponent) ComponentName() name.ComponentName {
473	return c.CommonComponentFields.ComponentName
474}
475
476// ResourceName implements the IstioComponent interface.
477func (c *EgressComponent) ResourceName() string {
478	return c.CommonComponentFields.ResourceName
479}
480
481// Namespace implements the IstioComponent interface.
482func (c *EgressComponent) Namespace() string {
483	return c.CommonComponentFields.Namespace
484}
485
486// Enabled implements the IstioComponent interface.
487func (c *EgressComponent) Enabled() bool {
488	// type assert is guaranteed to work in this context.
489	return boolValue(c.componentSpec.(*v1alpha1.GatewaySpec).Enabled)
490}
491
492// AddonComponent is an external component.
493type AddonComponent struct {
494	*CommonComponentFields
495}
496
497// NewAddonComponent creates a new IngressComponent and returns a pointer to it.
498func NewAddonComponent(addonName, resourceName string, spec *v1alpha1.ExternalComponentSpec, opts *Options) *AddonComponent {
499	return &AddonComponent{
500		CommonComponentFields: &CommonComponentFields{
501			Options:       opts,
502			ComponentName: name.AddonComponentName,
503			ResourceName:  resourceName,
504			addonName:     addonName,
505			componentSpec: spec,
506		},
507	}
508}
509
510// Run implements the IstioComponent interface.
511func (c *AddonComponent) Run() error {
512	return runComponent(c.CommonComponentFields)
513}
514
515// RenderManifest implements the IstioComponent interface.
516func (c *AddonComponent) RenderManifest() (string, error) {
517	return renderManifest(c, c.CommonComponentFields)
518}
519
520// ComponentName implements the IstioComponent interface.
521func (c *AddonComponent) ComponentName() name.ComponentName {
522	return c.CommonComponentFields.ComponentName
523}
524
525// ResourceName implements the IstioComponent interface.
526func (c *AddonComponent) ResourceName() string {
527	return c.CommonComponentFields.ResourceName
528}
529
530// Namespace implements the IstioComponent interface.
531func (c *AddonComponent) Namespace() string {
532	return c.CommonComponentFields.Namespace
533}
534
535// Enabled implements the IstioComponent interface.
536func (c *AddonComponent) Enabled() bool {
537	// type assert is guaranteed to work in this context.
538	return boolValue(c.componentSpec.(*v1alpha1.ExternalComponentSpec).Enabled)
539}
540
541// runComponent performs startup tasks for the component defined by the given CommonComponentFields.
542func runComponent(c *CommonComponentFields) error {
543	r, err := createHelmRenderer(c)
544	if err != nil {
545		return err
546	}
547	if err := r.Run(); err != nil {
548		return err
549	}
550	c.renderer = r
551	c.started = true
552	return nil
553}
554
555// renderManifest renders the manifest for the component defined by c and returns the resulting string.
556func renderManifest(c IstioComponent, cf *CommonComponentFields) (string, error) {
557	if !cf.started {
558		return "", fmt.Errorf("component %s not started in RenderManifest", cf.ComponentName)
559	}
560
561	if !c.Enabled() {
562		return disabledYAMLStr(cf.ComponentName, cf.ResourceName), nil
563	}
564
565	mergedYAML, err := cf.Translator.TranslateHelmValues(cf.InstallSpec, cf.componentSpec, cf.ComponentName)
566	if err != nil {
567		return "", err
568	}
569
570	log.Debugf("Merged values:\n%s\n", mergedYAML)
571
572	my, err := cf.renderer.RenderManifest(mergedYAML)
573	if err != nil {
574		log.Errorf("Error rendering the manifest: %s", err)
575		return "", err
576	}
577	my += helm.YAMLSeparator + "\n"
578	if devDbg {
579		scope.Infof("Initial manifest with merged values:\n%s\n", my)
580	}
581	// Add the k8s resources from IstioOperatorSpec.
582	my, err = cf.Translator.OverlayK8sSettings(my, cf.InstallSpec, cf.ComponentName, cf.ResourceName, cf.addonName, cf.index)
583	if err != nil {
584		return "", err
585	}
586	cnOutput := string(cf.ComponentName)
587	if !cf.ComponentName.IsCoreComponent() && !cf.ComponentName.IsGateway() {
588		cnOutput += " " + cf.addonName
589	}
590	my = "# Resources for " + cnOutput + " component\n\n" + my
591	if devDbg {
592		scope.Infof("Manifest after k8s API settings:\n%s\n", my)
593	}
594	// Add the k8s resource overlays from IstioOperatorSpec.
595	pathToK8sOverlay := ""
596	if !cf.ComponentName.IsCoreComponent() && !cf.ComponentName.IsGateway() {
597		pathToK8sOverlay += fmt.Sprintf("AddonComponents.%s.", cf.addonName)
598	} else {
599		pathToK8sOverlay += fmt.Sprintf("Components.%s.", cf.ComponentName)
600		if cf.ComponentName == name.IngressComponentName || cf.ComponentName == name.EgressComponentName {
601			pathToK8sOverlay += fmt.Sprintf("%d.", cf.index)
602		}
603	}
604	pathToK8sOverlay += "K8S.Overlays"
605	var overlays []*v1alpha1.K8SObjectOverlay
606	found, err := tpath.SetFromPath(cf.InstallSpec, pathToK8sOverlay, &overlays)
607	if err != nil {
608		return "", err
609	}
610	if !found {
611		log.Debugf("Manifest after resources: \n%s\n", my)
612		return my, nil
613	}
614	kyo, err := yaml.Marshal(overlays)
615	if err != nil {
616		return "", err
617	}
618	scope.Infof("Applying Kubernetes overlay: \n%s\n", kyo)
619	ret, err := patch.YAMLManifestPatch(my, cf.Namespace, overlays)
620	if err != nil {
621		return "", err
622	}
623
624	scope.Debugf("Manifest after resources and overlay: \n%s\n", ret)
625	return ret, nil
626}
627
628// createHelmRenderer creates a helm renderer for the component defined by c and returns a ptr to it.
629// If a helm subdir is not found in ComponentMap translations, it is assumed to be "addon/<component name>.
630func createHelmRenderer(c *CommonComponentFields) (helm.TemplateRenderer, error) {
631	iop := c.InstallSpec
632	cns := string(c.ComponentName)
633	if c.ComponentName.IsAddon() {
634		// For addons, distinguish the chart path using the addon name.
635		cns = c.addonName
636	}
637	helmSubdir := addonsChartDirName + "/" + cns
638	if cm := c.Translator.ComponentMap(cns); cm != nil {
639		helmSubdir = cm.HelmSubdir
640	}
641	return helm.NewHelmRenderer(iop.InstallPackagePath, helmSubdir, cns, c.Namespace)
642}
643
644func isCoreComponentEnabled(c *CommonComponentFields) bool {
645	enabled, err := c.Translator.IsComponentEnabled(c.ComponentName, c.InstallSpec)
646	if err != nil {
647		return false
648	}
649	return enabled
650}
651
652// disabledYAMLStr returns the YAML comment string that the given component is disabled.
653func disabledYAMLStr(componentName name.ComponentName, resourceName string) string {
654	fullName := string(componentName)
655	if resourceName != "" {
656		fullName += " " + resourceName
657	}
658	return fmt.Sprintf("%s %s %s\n", yamlCommentStr, fullName, componentDisabledStr)
659}
660
661// boolValue returns true is v is not nil and v.Value is true, or false otherwise.
662func boolValue(v *v1alpha1.BoolValueForPB) bool {
663	if v == nil {
664		return false
665	}
666	return v.Value
667}
668