| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| /* |
| Package component defines an in-memory representation of IstioOperator.<Feature>.<Component>. It provides functions |
| for manipulating the component and rendering a manifest from it. |
| See ../README.md for an architecture overview. |
| */ |
| package component |
| |
| import ( |
| "fmt" |
| ) |
| |
| import ( |
| "istio.io/api/operator/v1alpha1" |
| "istio.io/pkg/log" |
| "k8s.io/apimachinery/pkg/version" |
| "sigs.k8s.io/yaml" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/helm" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/metrics" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/name" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/patch" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/tpath" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/translate" |
| "github.com/apache/dubbo-go-pixiu/pkg/util/sets" |
| ) |
| |
| const ( |
| // String to emit for any component which is disabled. |
| componentDisabledStr = "component is disabled." |
| yamlCommentStr = "#" |
| ) |
| |
| var scope = log.RegisterScope("installer", "installer", 0) |
| |
| // Options defines options for a component. |
| type Options struct { |
| // installSpec is the global IstioOperatorSpec. |
| InstallSpec *v1alpha1.IstioOperatorSpec |
| // translator is the translator for this component. |
| Translator *translate.Translator |
| // Namespace is the namespace for this component. |
| Namespace string |
| // Filter is the filenames to render |
| Filter sets.Set |
| // Version is the Kubernetes version information. |
| Version *version.Info |
| } |
| |
| // IstioComponent defines the interface for a component. |
| type IstioComponent interface { |
| // ComponentName returns the name of the component. |
| ComponentName() name.ComponentName |
| // ResourceName returns the name of the resources of the component. |
| ResourceName() string |
| // Namespace returns the namespace for the component. |
| Namespace() string |
| // Enabled reports whether the component is enabled. |
| Enabled() bool |
| // Run starts the component. Must me called before the component is used. |
| Run() error |
| // RenderManifest returns a string with the rendered manifest for the component. |
| RenderManifest() (string, error) |
| } |
| |
| // CommonComponentFields is a struct common to all components. |
| type CommonComponentFields struct { |
| *Options |
| ComponentName name.ComponentName |
| // resourceName is the name of all resources for this component. |
| ResourceName string |
| // index is the index of the component (only used for components with multiple instances like gateways). |
| index int |
| // componentSpec for the actual component e.g. GatewaySpec, ComponentSpec. |
| componentSpec interface{} |
| // started reports whether the component is in initialized and running. |
| started bool |
| renderer helm.TemplateRenderer |
| } |
| |
| // NewCoreComponent creates a new IstioComponent with the given componentName and options. |
| func NewCoreComponent(cn name.ComponentName, opts *Options) IstioComponent { |
| var component IstioComponent |
| switch cn { |
| case name.IstioBaseComponentName: |
| component = NewCRDComponent(opts) |
| case name.PilotComponentName: |
| component = NewPilotComponent(opts) |
| case name.CNIComponentName: |
| component = NewCNIComponent(opts) |
| case name.IstiodRemoteComponentName: |
| component = NewIstiodRemoteComponent(opts) |
| default: |
| scope.Errorf("Unknown component componentName: " + string(cn)) |
| } |
| return component |
| } |
| |
| // BaseComponent is the base component. |
| type BaseComponent struct { |
| *CommonComponentFields |
| } |
| |
| // NewCRDComponent creates a new BaseComponent and returns a pointer to it. |
| func NewCRDComponent(opts *Options) *BaseComponent { |
| return &BaseComponent{ |
| &CommonComponentFields{ |
| Options: opts, |
| ComponentName: name.IstioBaseComponentName, |
| }, |
| } |
| } |
| |
| // Run implements the IstioComponent interface. |
| func (c *BaseComponent) Run() error { |
| return runComponent(c.CommonComponentFields) |
| } |
| |
| // RenderManifest implements the IstioComponent interface. |
| func (c *BaseComponent) RenderManifest() (string, error) { |
| return renderManifest(c, c.CommonComponentFields) |
| } |
| |
| // ComponentName implements the IstioComponent interface. |
| func (c *BaseComponent) ComponentName() name.ComponentName { |
| return c.CommonComponentFields.ComponentName |
| } |
| |
| // ResourceName implements the IstioComponent interface. |
| func (c *BaseComponent) ResourceName() string { |
| return c.CommonComponentFields.ResourceName |
| } |
| |
| // Namespace implements the IstioComponent interface. |
| func (c *BaseComponent) Namespace() string { |
| return c.CommonComponentFields.Namespace |
| } |
| |
| // Enabled implements the IstioComponent interface. |
| func (c *BaseComponent) Enabled() bool { |
| return isCoreComponentEnabled(c.CommonComponentFields) |
| } |
| |
| // PilotComponent is the pilot component. |
| type PilotComponent struct { |
| *CommonComponentFields |
| } |
| |
| // NewPilotComponent creates a new PilotComponent and returns a pointer to it. |
| func NewPilotComponent(opts *Options) *PilotComponent { |
| cn := name.PilotComponentName |
| return &PilotComponent{ |
| &CommonComponentFields{ |
| Options: opts, |
| ComponentName: cn, |
| ResourceName: opts.Translator.ComponentMaps[cn].ResourceName, |
| }, |
| } |
| } |
| |
| // Run implements the IstioComponent interface. |
| func (c *PilotComponent) Run() error { |
| return runComponent(c.CommonComponentFields) |
| } |
| |
| // RenderManifest implements the IstioComponent interface. |
| func (c *PilotComponent) RenderManifest() (string, error) { |
| return renderManifest(c, c.CommonComponentFields) |
| } |
| |
| // ComponentName implements the IstioComponent interface. |
| func (c *PilotComponent) ComponentName() name.ComponentName { |
| return c.CommonComponentFields.ComponentName |
| } |
| |
| // ResourceName implements the IstioComponent interface. |
| func (c *PilotComponent) ResourceName() string { |
| return c.CommonComponentFields.ResourceName |
| } |
| |
| // Namespace implements the IstioComponent interface. |
| func (c *PilotComponent) Namespace() string { |
| return c.CommonComponentFields.Namespace |
| } |
| |
| // Enabled implements the IstioComponent interface. |
| func (c *PilotComponent) Enabled() bool { |
| return isCoreComponentEnabled(c.CommonComponentFields) |
| } |
| |
| // CNIComponent is the istio cni component. |
| type CNIComponent struct { |
| *CommonComponentFields |
| } |
| |
| // NewCNIComponent creates a new NewCNIComponent and returns a pointer to it. |
| func NewCNIComponent(opts *Options) *CNIComponent { |
| cn := name.CNIComponentName |
| return &CNIComponent{ |
| &CommonComponentFields{ |
| Options: opts, |
| ComponentName: cn, |
| }, |
| } |
| } |
| |
| // Run implements the IstioComponent interface. |
| func (c *CNIComponent) Run() error { |
| return runComponent(c.CommonComponentFields) |
| } |
| |
| // RenderManifest implements the IstioComponent interface. |
| func (c *CNIComponent) RenderManifest() (string, error) { |
| return renderManifest(c, c.CommonComponentFields) |
| } |
| |
| // ComponentName implements the IstioComponent interface. |
| func (c *CNIComponent) ComponentName() name.ComponentName { |
| return c.CommonComponentFields.ComponentName |
| } |
| |
| // ResourceName implements the IstioComponent interface. |
| func (c *CNIComponent) ResourceName() string { |
| return c.CommonComponentFields.ResourceName |
| } |
| |
| // Namespace implements the IstioComponent interface. |
| func (c *CNIComponent) Namespace() string { |
| return c.CommonComponentFields.Namespace |
| } |
| |
| // Enabled implements the IstioComponent interface. |
| func (c *CNIComponent) Enabled() bool { |
| return isCoreComponentEnabled(c.CommonComponentFields) |
| } |
| |
| // IstiodRemoteComponent is the istiod remote component. |
| type IstiodRemoteComponent struct { |
| *CommonComponentFields |
| } |
| |
| // NewIstiodRemoteComponent creates a new NewIstiodRemoteComponent and returns a pointer to it. |
| func NewIstiodRemoteComponent(opts *Options) *IstiodRemoteComponent { |
| cn := name.IstiodRemoteComponentName |
| return &IstiodRemoteComponent{ |
| &CommonComponentFields{ |
| Options: opts, |
| ComponentName: cn, |
| }, |
| } |
| } |
| |
| // Run implements the IstioComponent interface. |
| func (c *IstiodRemoteComponent) Run() error { |
| return runComponent(c.CommonComponentFields) |
| } |
| |
| // RenderManifest implements the IstioComponent interface. |
| func (c *IstiodRemoteComponent) RenderManifest() (string, error) { |
| return renderManifest(c, c.CommonComponentFields) |
| } |
| |
| // ComponentName implements the IstioComponent interface. |
| func (c *IstiodRemoteComponent) ComponentName() name.ComponentName { |
| return c.CommonComponentFields.ComponentName |
| } |
| |
| // ResourceName implements the IstioComponent interface. |
| func (c *IstiodRemoteComponent) ResourceName() string { |
| return c.CommonComponentFields.ResourceName |
| } |
| |
| // Namespace implements the IstioComponent interface. |
| func (c *IstiodRemoteComponent) Namespace() string { |
| return c.CommonComponentFields.Namespace |
| } |
| |
| // Enabled implements the IstioComponent interface. |
| func (c *IstiodRemoteComponent) Enabled() bool { |
| return isCoreComponentEnabled(c.CommonComponentFields) |
| } |
| |
| // IngressComponent is the ingress gateway component. |
| type IngressComponent struct { |
| *CommonComponentFields |
| } |
| |
| // NewIngressComponent creates a new IngressComponent and returns a pointer to it. |
| func NewIngressComponent(resourceName string, index int, spec *v1alpha1.GatewaySpec, opts *Options) *IngressComponent { |
| cn := name.IngressComponentName |
| return &IngressComponent{ |
| CommonComponentFields: &CommonComponentFields{ |
| Options: opts, |
| ComponentName: cn, |
| ResourceName: resourceName, |
| index: index, |
| componentSpec: spec, |
| }, |
| } |
| } |
| |
| // Run implements the IstioComponent interface. |
| func (c *IngressComponent) Run() error { |
| return runComponent(c.CommonComponentFields) |
| } |
| |
| // RenderManifest implements the IstioComponent interface. |
| func (c *IngressComponent) RenderManifest() (string, error) { |
| return renderManifest(c, c.CommonComponentFields) |
| } |
| |
| // ComponentName implements the IstioComponent interface. |
| func (c *IngressComponent) ComponentName() name.ComponentName { |
| return c.CommonComponentFields.ComponentName |
| } |
| |
| // ResourceName implements the IstioComponent interface. |
| func (c *IngressComponent) ResourceName() string { |
| return c.CommonComponentFields.ResourceName |
| } |
| |
| // Namespace implements the IstioComponent interface. |
| func (c *IngressComponent) Namespace() string { |
| return c.CommonComponentFields.Namespace |
| } |
| |
| // Enabled implements the IstioComponent interface. |
| func (c *IngressComponent) Enabled() bool { |
| // type assert is guaranteed to work in this context. |
| return c.componentSpec.(*v1alpha1.GatewaySpec).Enabled.GetValue() |
| } |
| |
| // EgressComponent is the egress gateway component. |
| type EgressComponent struct { |
| *CommonComponentFields |
| } |
| |
| // NewEgressComponent creates a new IngressComponent and returns a pointer to it. |
| func NewEgressComponent(resourceName string, index int, spec *v1alpha1.GatewaySpec, opts *Options) *EgressComponent { |
| cn := name.EgressComponentName |
| return &EgressComponent{ |
| CommonComponentFields: &CommonComponentFields{ |
| Options: opts, |
| ComponentName: cn, |
| index: index, |
| componentSpec: spec, |
| ResourceName: resourceName, |
| }, |
| } |
| } |
| |
| // Run implements the IstioComponent interface. |
| func (c *EgressComponent) Run() error { |
| return runComponent(c.CommonComponentFields) |
| } |
| |
| // RenderManifest implements the IstioComponent interface. |
| func (c *EgressComponent) RenderManifest() (string, error) { |
| return renderManifest(c, c.CommonComponentFields) |
| } |
| |
| // ComponentName implements the IstioComponent interface. |
| func (c *EgressComponent) ComponentName() name.ComponentName { |
| return c.CommonComponentFields.ComponentName |
| } |
| |
| // ResourceName implements the IstioComponent interface. |
| func (c *EgressComponent) ResourceName() string { |
| return c.CommonComponentFields.ResourceName |
| } |
| |
| // Namespace implements the IstioComponent interface. |
| func (c *EgressComponent) Namespace() string { |
| return c.CommonComponentFields.Namespace |
| } |
| |
| // Enabled implements the IstioComponent interface. |
| func (c *EgressComponent) Enabled() bool { |
| // type assert is guaranteed to work in this context. |
| return c.componentSpec.(*v1alpha1.GatewaySpec).Enabled.GetValue() |
| } |
| |
| // runComponent performs startup tasks for the component defined by the given CommonComponentFields. |
| func runComponent(c *CommonComponentFields) error { |
| r := createHelmRenderer(c) |
| if err := r.Run(); err != nil { |
| return err |
| } |
| c.renderer = r |
| c.started = true |
| return nil |
| } |
| |
| // renderManifest renders the manifest for the component defined by c and returns the resulting string. |
| func renderManifest(c IstioComponent, cf *CommonComponentFields) (string, error) { |
| if !cf.started { |
| metrics.CountManifestRenderError(c.ComponentName(), metrics.RenderNotStartedError) |
| return "", fmt.Errorf("component %s not started in RenderManifest", cf.ComponentName) |
| } |
| |
| if !c.Enabled() { |
| return disabledYAMLStr(cf.ComponentName, cf.ResourceName), nil |
| } |
| |
| mergedYAML, err := cf.Translator.TranslateHelmValues(cf.InstallSpec, cf.componentSpec, cf.ComponentName) |
| if err != nil { |
| metrics.CountManifestRenderError(c.ComponentName(), metrics.HelmTranslateIOPToValuesError) |
| return "", err |
| } |
| |
| scope.Debugf("Merged values:\n%s\n", mergedYAML) |
| |
| my, err := cf.renderer.RenderManifestFiltered(mergedYAML, func(s string) bool { |
| return len(cf.Filter) == 0 || cf.Filter.Contains(s) |
| }) |
| if err != nil { |
| log.Errorf("Error rendering the manifest: %s", err) |
| metrics.CountManifestRenderError(c.ComponentName(), metrics.HelmChartRenderError) |
| return "", err |
| } |
| my += helm.YAMLSeparator + "\n" |
| scope.Debugf("Initial manifest with merged values:\n%s\n", my) |
| |
| // Add the k8s resources from IstioOperatorSpec. |
| my, err = cf.Translator.OverlayK8sSettings(my, cf.InstallSpec, cf.ComponentName, cf.ResourceName, cf.index) |
| if err != nil { |
| metrics.CountManifestRenderError(c.ComponentName(), metrics.K8SSettingsOverlayError) |
| return "", err |
| } |
| cnOutput := string(cf.ComponentName) |
| my = "# Resources for " + cnOutput + " component\n\n" + my |
| scope.Debugf("Manifest after k8s API settings:\n%s\n", my) |
| |
| // Add the k8s resource overlays from IstioOperatorSpec. |
| pathToK8sOverlay := fmt.Sprintf("Components.%s.", cf.ComponentName) |
| if cf.ComponentName == name.IngressComponentName || cf.ComponentName == name.EgressComponentName { |
| pathToK8sOverlay += fmt.Sprintf("%d.", cf.index) |
| } |
| |
| pathToK8sOverlay += "K8S.Overlays" |
| var overlays []*v1alpha1.K8SObjectOverlay |
| found, err := tpath.SetFromPath(cf.InstallSpec, pathToK8sOverlay, &overlays) |
| if err != nil { |
| return "", err |
| } |
| if !found { |
| scope.Debugf("Manifest after resources: \n%s\n", my) |
| metrics.CountManifestRender(cf.ComponentName) |
| return my, nil |
| } |
| kyo, err := yaml.Marshal(overlays) |
| if err != nil { |
| return "", err |
| } |
| scope.Infof("Applying Kubernetes overlay: \n%s\n", kyo) |
| ret, err := patch.YAMLManifestPatch(my, cf.Namespace, overlays) |
| if err != nil { |
| metrics.CountManifestRenderError(c.ComponentName(), metrics.K8SManifestPatchError) |
| return "", err |
| } |
| |
| scope.Debugf("Manifest after resources and overlay: \n%s\n", ret) |
| metrics.CountManifestRender(cf.ComponentName) |
| return ret, nil |
| } |
| |
| // createHelmRenderer creates a helm renderer for the component defined by c and returns a ptr to it. |
| // If a helm subdir is not found in ComponentMap translations, it is assumed to be "addon/<component name>. |
| func createHelmRenderer(c *CommonComponentFields) helm.TemplateRenderer { |
| iop := c.InstallSpec |
| cns := string(c.ComponentName) |
| helmSubdir := c.Translator.ComponentMap(cns).HelmSubdir |
| return helm.NewHelmRenderer(iop.InstallPackagePath, helmSubdir, cns, c.Namespace, c.Version) |
| } |
| |
| func isCoreComponentEnabled(c *CommonComponentFields) bool { |
| enabled, err := c.Translator.IsComponentEnabled(c.ComponentName, c.InstallSpec) |
| if err != nil { |
| return false |
| } |
| return enabled |
| } |
| |
| // disabledYAMLStr returns the YAML comment string that the given component is disabled. |
| func disabledYAMLStr(componentName name.ComponentName, resourceName string) string { |
| fullName := string(componentName) |
| if resourceName != "" { |
| fullName += " " + resourceName |
| } |
| return fmt.Sprintf("%s %s %s\n", yamlCommentStr, fullName, componentDisabledStr) |
| } |