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 }