istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/component/component.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  /*
    16  Package component defines an in-memory representation of IstioOperator.<Feature>.<Component>. It provides functions
    17  for manipulating the component and rendering a manifest from it.
    18  See ../README.md for an architecture overview.
    19  */
    20  package component
    21  
    22  import (
    23  	"fmt"
    24  
    25  	"k8s.io/apimachinery/pkg/version"
    26  	"sigs.k8s.io/yaml"
    27  
    28  	"istio.io/api/operator/v1alpha1"
    29  	"istio.io/istio/operator/pkg/helm"
    30  	"istio.io/istio/operator/pkg/metrics"
    31  	"istio.io/istio/operator/pkg/name"
    32  	"istio.io/istio/operator/pkg/patch"
    33  	"istio.io/istio/operator/pkg/tpath"
    34  	"istio.io/istio/operator/pkg/translate"
    35  	"istio.io/istio/pkg/log"
    36  	"istio.io/istio/pkg/util/sets"
    37  )
    38  
    39  const (
    40  	// String to emit for any component which is disabled.
    41  	componentDisabledStr = "component is disabled."
    42  	yamlCommentStr       = "#"
    43  )
    44  
    45  var scope = log.RegisterScope("installer", "installer")
    46  
    47  // Options defines options for a component.
    48  type Options struct {
    49  	// installSpec is the global IstioOperatorSpec.
    50  	InstallSpec *v1alpha1.IstioOperatorSpec
    51  	// translator is the translator for this component.
    52  	Translator *translate.Translator
    53  	// Namespace is the namespace for this component.
    54  	Namespace string
    55  	// Filter is the filenames to render
    56  	Filter sets.String
    57  	// Version is the Kubernetes version information.
    58  	Version *version.Info
    59  }
    60  
    61  // IstioComponent defines the interface for a component.
    62  type 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 be 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.
    78  type CommonComponentFields struct {
    79  	*Options
    80  	ComponentName name.ComponentName
    81  	// resourceName is the name of all resources for this component.
    82  	ResourceName string
    83  	// index is the index of the component (only used for components with multiple instances like gateways).
    84  	index int
    85  	// componentSpec for the actual component e.g. GatewaySpec, ComponentSpec.
    86  	componentSpec any
    87  	// started reports whether the component is in initialized and running.
    88  	started  bool
    89  	renderer helm.TemplateRenderer
    90  }
    91  
    92  type IstioComponentBase struct {
    93  	*CommonComponentFields
    94  }
    95  
    96  func (c *IstioComponentBase) ComponentName() name.ComponentName {
    97  	return c.CommonComponentFields.ComponentName
    98  }
    99  
   100  func (c *IstioComponentBase) ResourceName() string {
   101  	return c.CommonComponentFields.ResourceName
   102  }
   103  
   104  func (c *IstioComponentBase) Namespace() string {
   105  	return c.CommonComponentFields.Namespace
   106  }
   107  
   108  func (c *IstioComponentBase) Enabled() bool {
   109  	if c.CommonComponentFields.ComponentName.IsGateway() {
   110  		// type assert is guaranteed to work in this context.
   111  		return c.componentSpec.(*v1alpha1.GatewaySpec).Enabled.GetValue()
   112  	}
   113  	return isCoreComponentEnabled(c.CommonComponentFields)
   114  }
   115  
   116  func (c *IstioComponentBase) Run() error {
   117  	return runComponent(c.CommonComponentFields)
   118  }
   119  
   120  func (c *IstioComponentBase) RenderManifest() (string, error) {
   121  	return renderManifest(c)
   122  }
   123  
   124  // NewCoreComponent creates a new IstioComponent with the given componentName and options.
   125  func NewCoreComponent(cn name.ComponentName, opts *Options) IstioComponent {
   126  	var component IstioComponent
   127  	switch cn {
   128  	case name.IstioBaseComponentName:
   129  		component = NewCRDComponent(opts)
   130  	case name.PilotComponentName:
   131  		component = NewPilotComponent(opts)
   132  	case name.CNIComponentName:
   133  		component = NewCNIComponent(opts)
   134  	case name.IstiodRemoteComponentName:
   135  		component = NewIstiodRemoteComponent(opts)
   136  	case name.ZtunnelComponentName:
   137  		component = NewZtunnelComponent(opts)
   138  	default:
   139  		scope.Errorf("Unknown component componentName: " + string(cn))
   140  	}
   141  	return component
   142  }
   143  
   144  // BaseComponent is the base component.
   145  type BaseComponent struct {
   146  	*IstioComponentBase
   147  }
   148  
   149  // NewCRDComponent creates a new BaseComponent and returns a pointer to it.
   150  func NewCRDComponent(opts *Options) *BaseComponent {
   151  	return &BaseComponent{
   152  		&IstioComponentBase{
   153  			&CommonComponentFields{
   154  				Options:       opts,
   155  				ComponentName: name.IstioBaseComponentName,
   156  			},
   157  		},
   158  	}
   159  }
   160  
   161  // PilotComponent is the pilot component.
   162  type PilotComponent struct {
   163  	*IstioComponentBase
   164  }
   165  
   166  // NewPilotComponent creates a new PilotComponent and returns a pointer to it.
   167  func NewPilotComponent(opts *Options) *PilotComponent {
   168  	cn := name.PilotComponentName
   169  	return &PilotComponent{
   170  		&IstioComponentBase{
   171  			&CommonComponentFields{
   172  				Options:       opts,
   173  				ComponentName: cn,
   174  				ResourceName:  opts.Translator.ComponentMaps[cn].ResourceName,
   175  			},
   176  		},
   177  	}
   178  }
   179  
   180  type CNIComponent struct {
   181  	*IstioComponentBase
   182  }
   183  
   184  // NewCNIComponent creates a new NewCNIComponent and returns a pointer to it.
   185  func NewCNIComponent(opts *Options) *CNIComponent {
   186  	cn := name.CNIComponentName
   187  	return &CNIComponent{
   188  		&IstioComponentBase{
   189  			&CommonComponentFields{
   190  				Options:       opts,
   191  				ComponentName: cn,
   192  			},
   193  		},
   194  	}
   195  }
   196  
   197  // IstiodRemoteComponent is the istiod remote component.
   198  type IstiodRemoteComponent struct {
   199  	*IstioComponentBase
   200  }
   201  
   202  // NewIstiodRemoteComponent creates a new NewIstiodRemoteComponent and returns a pointer to it.
   203  func NewIstiodRemoteComponent(opts *Options) *IstiodRemoteComponent {
   204  	cn := name.IstiodRemoteComponentName
   205  	return &IstiodRemoteComponent{
   206  		&IstioComponentBase{
   207  			&CommonComponentFields{
   208  				Options:       opts,
   209  				ComponentName: cn,
   210  			},
   211  		},
   212  	}
   213  }
   214  
   215  // IngressComponent is the ingress gateway component.
   216  type IngressComponent struct {
   217  	*IstioComponentBase
   218  }
   219  
   220  // NewIngressComponent creates a new IngressComponent and returns a pointer to it.
   221  func NewIngressComponent(resourceName string, index int, spec *v1alpha1.GatewaySpec, opts *Options) *IngressComponent {
   222  	cn := name.IngressComponentName
   223  	return &IngressComponent{
   224  		&IstioComponentBase{
   225  			CommonComponentFields: &CommonComponentFields{
   226  				Options:       opts,
   227  				ComponentName: cn,
   228  				ResourceName:  resourceName,
   229  				index:         index,
   230  				componentSpec: spec,
   231  			},
   232  		},
   233  	}
   234  }
   235  
   236  // EgressComponent is the egress gateway component.
   237  type EgressComponent struct {
   238  	*IstioComponentBase
   239  }
   240  
   241  // NewEgressComponent creates a new IngressComponent and returns a pointer to it.
   242  func NewEgressComponent(resourceName string, index int, spec *v1alpha1.GatewaySpec, opts *Options) *EgressComponent {
   243  	cn := name.EgressComponentName
   244  	return &EgressComponent{
   245  		&IstioComponentBase{
   246  			CommonComponentFields: &CommonComponentFields{
   247  				Options:       opts,
   248  				ComponentName: cn,
   249  				index:         index,
   250  				componentSpec: spec,
   251  				ResourceName:  resourceName,
   252  			},
   253  		},
   254  	}
   255  }
   256  
   257  // ZtunnelComponent is the istio ztunnel component.
   258  type ZtunnelComponent struct {
   259  	*IstioComponentBase
   260  }
   261  
   262  // NewZtunnelComponent creates a new ZtunnelComponent and returns a pointer to it.
   263  func NewZtunnelComponent(opts *Options) *ZtunnelComponent {
   264  	return &ZtunnelComponent{
   265  		&IstioComponentBase{
   266  			&CommonComponentFields{
   267  				Options:       opts,
   268  				ComponentName: name.ZtunnelComponentName,
   269  			},
   270  		},
   271  	}
   272  }
   273  
   274  // runComponent performs startup tasks for the component defined by the given CommonComponentFields.
   275  func runComponent(c *CommonComponentFields) error {
   276  	r := createHelmRenderer(c)
   277  	if err := r.Run(); err != nil {
   278  		return err
   279  	}
   280  	c.renderer = r
   281  	c.started = true
   282  	return nil
   283  }
   284  
   285  // renderManifest renders the manifest for the component defined by c and returns the resulting string.
   286  func renderManifest(cf *IstioComponentBase) (string, error) {
   287  	if !cf.started {
   288  		metrics.CountManifestRenderError(cf.ComponentName(), metrics.RenderNotStartedError)
   289  		return "", fmt.Errorf("component %s not started in RenderManifest", cf.CommonComponentFields.ComponentName)
   290  	}
   291  
   292  	if !cf.Enabled() {
   293  		return disabledYAMLStr(cf.ComponentName(), cf.CommonComponentFields.ResourceName), nil
   294  	}
   295  
   296  	mergedYAML, err := cf.Translator.TranslateHelmValues(cf.InstallSpec, cf.componentSpec, cf.ComponentName())
   297  	if err != nil {
   298  		metrics.CountManifestRenderError(cf.ComponentName(), metrics.HelmTranslateIOPToValuesError)
   299  		return "", err
   300  	}
   301  
   302  	scope.Debugf("Merged values:\n%s\n", mergedYAML)
   303  
   304  	my, err := cf.renderer.RenderManifestFiltered(mergedYAML, func(s string) bool {
   305  		return cf.Filter.IsEmpty() || cf.Filter.Contains(s)
   306  	})
   307  	if err != nil {
   308  		log.Errorf("Error rendering the manifest: %s", err)
   309  		metrics.CountManifestRenderError(cf.ComponentName(), metrics.HelmChartRenderError)
   310  		return "", err
   311  	}
   312  	my += helm.YAMLSeparator + "\n"
   313  	scope.Debugf("Initial manifest with merged values:\n%s\n", my)
   314  
   315  	// Add the k8s resources from IstioOperatorSpec.
   316  	my, err = cf.Translator.OverlayK8sSettings(my, cf.InstallSpec, cf.CommonComponentFields.ComponentName,
   317  		cf.CommonComponentFields.ResourceName, cf.index)
   318  	if err != nil {
   319  		metrics.CountManifestRenderError(cf.ComponentName(), metrics.K8SSettingsOverlayError)
   320  		return "", err
   321  	}
   322  	cnOutput := string(cf.CommonComponentFields.ComponentName)
   323  	my = "# Resources for " + cnOutput + " component\n\n" + my
   324  	scope.Debugf("Manifest after k8s API settings:\n%s\n", my)
   325  
   326  	// Add the k8s resource overlays from IstioOperatorSpec.
   327  	pathToK8sOverlay := fmt.Sprintf("Components.%s.", cf.CommonComponentFields.ComponentName)
   328  	if cf.CommonComponentFields.ComponentName.IsGateway() {
   329  		pathToK8sOverlay += fmt.Sprintf("%d.", cf.index)
   330  	}
   331  
   332  	pathToK8sOverlay += "K8S.Overlays"
   333  	var overlays []*v1alpha1.K8SObjectOverlay
   334  	found, err := tpath.SetFromPath(cf.InstallSpec, pathToK8sOverlay, &overlays)
   335  	if err != nil {
   336  		return "", err
   337  	}
   338  	if !found {
   339  		scope.Debugf("Manifest after resources: \n%s\n", my)
   340  		metrics.CountManifestRender(cf.ComponentName())
   341  		return my, nil
   342  	}
   343  	kyo, err := yaml.Marshal(overlays)
   344  	if err != nil {
   345  		return "", err
   346  	}
   347  	scope.Infof("Applying Kubernetes overlay: \n%s\n", kyo)
   348  	ret, err := patch.YAMLManifestPatch(my, cf.Namespace(), overlays)
   349  	if err != nil {
   350  		metrics.CountManifestRenderError(cf.ComponentName(), metrics.K8SManifestPatchError)
   351  		return "", err
   352  	}
   353  
   354  	scope.Debugf("Manifest after resources and overlay: \n%s\n", ret)
   355  	metrics.CountManifestRender(cf.ComponentName())
   356  	return ret, nil
   357  }
   358  
   359  // createHelmRenderer creates a helm renderer for the component defined by c and returns a ptr to it.
   360  // If a helm subdir is not found in ComponentMap translations, it is assumed to be "addon/<component name>".
   361  func createHelmRenderer(c *CommonComponentFields) helm.TemplateRenderer {
   362  	iop := c.InstallSpec
   363  	cns := string(c.ComponentName)
   364  	helmSubdir := c.Translator.ComponentMap(cns).HelmSubdir
   365  	return helm.NewHelmRenderer(iop.InstallPackagePath, helmSubdir, cns, c.Namespace, c.Version)
   366  }
   367  
   368  func isCoreComponentEnabled(c *CommonComponentFields) bool {
   369  	enabled, err := c.Translator.IsComponentEnabled(c.ComponentName, c.InstallSpec)
   370  	if err != nil {
   371  		return false
   372  	}
   373  	return enabled
   374  }
   375  
   376  // disabledYAMLStr returns the YAML comment string that the given component is disabled.
   377  func disabledYAMLStr(componentName name.ComponentName, resourceName string) string {
   378  	fullName := string(componentName)
   379  	if resourceName != "" {
   380  		fullName += " " + resourceName
   381  	}
   382  	return fmt.Sprintf("%s %s %s\n", yamlCommentStr, fullName, componentDisabledStr)
   383  }