github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/builder/template/component_wrapper.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package template 21 22 import ( 23 "context" 24 "fmt" 25 "os" 26 "path/filepath" 27 "reflect" 28 "strings" 29 30 corev1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 "sigs.k8s.io/controller-runtime/pkg/log" 34 "sigs.k8s.io/yaml" 35 36 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 37 "github.com/1aal/kubeblocks/controllers/apps/components" 38 "github.com/1aal/kubeblocks/pkg/cli/printer" 39 "github.com/1aal/kubeblocks/pkg/configuration/core" 40 "github.com/1aal/kubeblocks/pkg/constant" 41 "github.com/1aal/kubeblocks/pkg/controller/builder" 42 "github.com/1aal/kubeblocks/pkg/controller/component" 43 "github.com/1aal/kubeblocks/pkg/controller/factory" 44 "github.com/1aal/kubeblocks/pkg/controller/graph" 45 "github.com/1aal/kubeblocks/pkg/controller/model" 46 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 47 "github.com/1aal/kubeblocks/pkg/generics" 48 ) 49 50 type templateRenderWorkflow struct { 51 renderedOpts RenderedOptions 52 clusterYaml string 53 localObjects []client.Object 54 55 clusterDefObj *appsv1alpha1.ClusterDefinition 56 clusterVersionObj *appsv1alpha1.ClusterVersion 57 58 clusterDefComponents []appsv1alpha1.ClusterComponentDefinition 59 clusterVersionComponents []appsv1alpha1.ClusterComponentVersion 60 } 61 62 func (w *templateRenderWorkflow) Do(outputDir string) error { 63 var err error 64 var cluster *appsv1alpha1.Cluster 65 66 cli := newMockClient(w.localObjects) 67 ctx := intctrlutil.RequestCtx{ 68 Ctx: context.Background(), 69 Log: log.Log.WithName("ctool"), 70 } 71 72 if cluster, err = w.createClusterObject(); err != nil { 73 return err 74 } 75 ctx.Log.V(1).Info(fmt.Sprintf("cluster object : %v", cluster)) 76 77 components := w.clusterDefComponents 78 if w.renderedOpts.ConfigSpec != "" { 79 comp := w.getComponentWithConfigSpec(w.renderedOpts.ConfigSpec) 80 if comp == nil { 81 return core.MakeError("config spec[%s] is not found", w.renderedOpts.ConfigSpec) 82 } 83 components = []appsv1alpha1.ClusterComponentDefinition{*comp} 84 } 85 86 for _, component := range components { 87 synthesizedComponent, objects, err := generateComponentObjects(w, ctx, cli, component.Name, cluster) 88 if err != nil { 89 return err 90 } 91 if len(objects) == 0 { 92 continue 93 } 94 if err := w.dumpRenderedTemplates(outputDir, objects, component, w.renderedOpts.ConfigSpec, synthesizedComponent, cluster); err != nil { 95 return err 96 } 97 } 98 return nil 99 } 100 101 func (w *templateRenderWorkflow) getComponentName(componentType string, cluster *appsv1alpha1.Cluster) (string, error) { 102 clusterCompSpec := cluster.Spec.GetDefNameMappingComponents()[componentType] 103 if len(clusterCompSpec) == 0 { 104 return "", core.MakeError("component[%s] is not defined in cluster definition", componentType) 105 } 106 return clusterCompSpec[0].Name, nil 107 } 108 109 func checkTemplateExist[T any](arrs []T, name string) bool { 110 for _, a := range arrs { 111 v := reflect.ValueOf(a) 112 if v.Kind() == reflect.Ptr { 113 v = v.Elem() 114 } 115 v = v.FieldByName("Name") 116 if v.Kind() == reflect.String && v.String() == name { 117 return true 118 } 119 } 120 return false 121 } 122 123 func (w *templateRenderWorkflow) getComponentWithConfigSpec(name string) *appsv1alpha1.ClusterComponentDefinition { 124 var compMap map[string]*appsv1alpha1.ClusterComponentVersion 125 126 if w.clusterVersionObj != nil { 127 compMap = w.clusterVersionObj.Spec.GetDefNameMappingComponents() 128 } 129 for _, component := range w.clusterDefComponents { 130 if checkTemplateExist(component.ConfigSpecs, name) { 131 return &component 132 } 133 if checkTemplateExist(component.ScriptSpecs, name) { 134 return &component 135 } 136 if compMap != nil && compMap[component.Name] != nil { 137 if checkTemplateExist(compMap[component.Name].ConfigSpecs, name) { 138 return &component 139 } 140 } 141 } 142 return nil 143 } 144 145 func (w *templateRenderWorkflow) createClusterObject() (*appsv1alpha1.Cluster, error) { 146 if w.clusterYaml != "" { 147 return CustomizedObjFromYaml(w.clusterYaml, generics.ClusterSignature) 148 } 149 return mockClusterObject(w.clusterDefObj, w.renderedOpts, w.clusterVersionObj), nil 150 } 151 152 func (w *templateRenderWorkflow) dumpRenderedTemplates(outputDir string, objects []client.Object, componentDef appsv1alpha1.ClusterComponentDefinition, name string, synthesizedComponent *component.SynthesizedComponent, cluster *appsv1alpha1.Cluster) error { 153 fromTemplate := func(component *component.SynthesizedComponent) []appsv1alpha1.ComponentTemplateSpec { 154 templates := make([]appsv1alpha1.ComponentTemplateSpec, 0, len(component.ConfigTemplates)+len(component.ScriptTemplates)) 155 for _, tpl := range component.ConfigTemplates { 156 templates = append(templates, tpl.ComponentTemplateSpec) 157 } 158 templates = append(templates, component.ScriptTemplates...) 159 return templates 160 } 161 foundConfigSpec := func(component *component.SynthesizedComponent, name string) *appsv1alpha1.ComponentConfigSpec { 162 for i := range component.ConfigTemplates { 163 template := &component.ConfigTemplates[i] 164 if template.Name == name { 165 return template 166 } 167 } 168 return nil 169 } 170 171 for _, template := range fromTemplate(synthesizedComponent) { 172 if name != "" && name != template.Name { 173 continue 174 } 175 comName, _ := w.getComponentName(componentDef.Name, cluster) 176 cfgName := core.GetComponentCfgName(cluster.Name, comName, template.Name) 177 if err := dumpTemplate(template, outputDir, objects, componentDef.Name, cfgName, foundConfigSpec(synthesizedComponent, template.Name)); err != nil { 178 return err 179 } 180 } 181 return nil 182 } 183 184 func getClusterDefComponents(clusterDefObj *appsv1alpha1.ClusterDefinition, componentName string) []appsv1alpha1.ClusterComponentDefinition { 185 if componentName == "" { 186 return clusterDefObj.Spec.ComponentDefs 187 } 188 component := clusterDefObj.GetComponentDefByName(componentName) 189 if component == nil { 190 return nil 191 } 192 return []appsv1alpha1.ClusterComponentDefinition{*component} 193 } 194 195 func getClusterVersionComponents(clusterVersionObj *appsv1alpha1.ClusterVersion, componentName string) []appsv1alpha1.ClusterComponentVersion { 196 if clusterVersionObj == nil { 197 return nil 198 } 199 if componentName == "" { 200 return clusterVersionObj.Spec.ComponentVersions 201 } 202 componentMap := clusterVersionObj.Spec.GetDefNameMappingComponents() 203 if component, ok := componentMap[componentName]; ok { 204 return []appsv1alpha1.ClusterComponentVersion{*component} 205 } 206 return nil 207 } 208 209 func NewWorkflowTemplateRender(helmTemplateDir string, opts RenderedOptions, clusterDef, clusterVersion string) (*templateRenderWorkflow, error) { 210 foundCVResource := func(allObjects []client.Object) *appsv1alpha1.ClusterVersion { 211 cvObj := GetTypedResourceObjectBySignature(allObjects, generics.ClusterVersionSignature, 212 func(object client.Object) bool { 213 if clusterVersion != "" { 214 return object.GetName() == clusterVersion 215 } 216 return object.GetAnnotations() != nil && object.GetAnnotations()[constant.DefaultClusterVersionAnnotationKey] == "true" 217 }) 218 if clusterVersion == "" && cvObj == nil { 219 cvObj = GetTypedResourceObjectBySignature(allObjects, generics.ClusterVersionSignature) 220 } 221 return cvObj 222 } 223 224 if _, err := os.Stat(helmTemplateDir); err != nil { 225 panic("cluster definition yaml file is required") 226 } 227 228 allObjects, err := CreateObjectsFromDirectory(helmTemplateDir) 229 if err != nil { 230 return nil, err 231 } 232 233 clusterDefObj := GetTypedResourceObjectBySignature(allObjects, generics.ClusterDefinitionSignature, WithResourceName(clusterDef)) 234 if clusterDefObj == nil { 235 return nil, core.MakeError("cluster definition object is not found in helm template directory[%s]", helmTemplateDir) 236 } 237 238 // hack apiserver auto filefield 239 checkAndFillPortProtocol(clusterDefObj.Spec.ComponentDefs) 240 241 var cdComponents []appsv1alpha1.ClusterComponentDefinition 242 if cdComponents = getClusterDefComponents(clusterDefObj, opts.ComponentName); cdComponents == nil { 243 return nil, core.MakeError("component[%s] is not defined in cluster definition", opts.ComponentName) 244 } 245 clusterVersionObj := foundCVResource(allObjects) 246 return &templateRenderWorkflow{ 247 renderedOpts: opts, 248 clusterDefObj: clusterDefObj, 249 clusterVersionObj: clusterVersionObj, 250 localObjects: allObjects, 251 clusterDefComponents: cdComponents, 252 clusterVersionComponents: getClusterVersionComponents(clusterVersionObj, opts.ComponentName), 253 }, nil 254 } 255 256 func dumpTemplate(template appsv1alpha1.ComponentTemplateSpec, outputDir string, objects []client.Object, componentDefName string, cfgName string, configSpec *appsv1alpha1.ComponentConfigSpec) error { 257 output := filepath.Join(outputDir, cfgName) 258 fmt.Printf("dump rendering template spec: %s, output directory: %s\n", 259 printer.BoldYellow(fmt.Sprintf("%s.%s", componentDefName, template.Name)), output) 260 261 if err := os.MkdirAll(output, 0755); err != nil { 262 return err 263 } 264 265 var ok bool 266 var cm *corev1.ConfigMap 267 for _, obj := range objects { 268 if cm, ok = obj.(*corev1.ConfigMap); !ok || !isTemplateOwner(cm, configSpec, cfgName) { 269 continue 270 } 271 if isTemplateObject(cm, cfgName) { 272 for file, val := range cm.Data { 273 if err := os.WriteFile(filepath.Join(output, file), []byte(val), 0755); err != nil { 274 return err 275 } 276 } 277 } 278 if isTemplateEnvFromObject(cm, configSpec, cfgName) { 279 val, err := yaml.Marshal(cm) 280 if err != nil { 281 return err 282 } 283 yamlFile := fmt.Sprintf("%s.yaml", cm.Name[len(cfgName)+1:]) 284 if err := os.WriteFile(filepath.Join(output, yamlFile), val, 0755); err != nil { 285 return err 286 } 287 } 288 } 289 return nil 290 } 291 292 func isTemplateObject(cm *corev1.ConfigMap, cfgName string) bool { 293 return cm.Name == cfgName 294 } 295 296 func isTemplateEnvFromObject(cm *corev1.ConfigMap, configSpec *appsv1alpha1.ComponentConfigSpec, cfgName string) bool { 297 if configSpec == nil || len(configSpec.AsEnvFrom) == 0 || configSpec.ConfigConstraintRef == "" || len(cm.Labels) == 0 { 298 return false 299 } 300 return cm.Labels[constant.CMTemplateNameLabelKey] == configSpec.Name && strings.HasPrefix(cm.Name, cfgName) 301 } 302 303 func isTemplateOwner(cm *corev1.ConfigMap, configSpec *appsv1alpha1.ComponentConfigSpec, cfgName string) bool { 304 return isTemplateObject(cm, cfgName) || isTemplateEnvFromObject(cm, configSpec, cfgName) 305 } 306 307 func generateComponentObjects(w *templateRenderWorkflow, ctx intctrlutil.RequestCtx, cli *mockClient, 308 componentType string, cluster *appsv1alpha1.Cluster) (*component.SynthesizedComponent, []client.Object, error) { 309 cmGVK := generics.ToGVK(&corev1.ConfigMap{}) 310 311 objs := make([]client.Object, 0) 312 cli.SetResourceHandler(&ResourceHandler{ 313 Matcher: []ResourceMatcher{func(obj runtime.Object) bool { 314 res := obj.(client.Object) 315 return generics.ToGVK(res) == cmGVK && 316 res.GetLabels() != nil && 317 res.GetLabels()[constant.CMTemplateNameLabelKey] != "" 318 }}, 319 Handler: func(obj runtime.Object) error { 320 objs = append(objs, obj.(client.Object)) 321 return nil 322 }, 323 }) 324 325 compName, err := w.getComponentName(componentType, cluster) 326 if err != nil { 327 return nil, nil, err 328 } 329 dag := graph.NewDAG() 330 root := builder.NewReplicatedStateMachineBuilder(cluster.Namespace, fmt.Sprintf("%s-%s", cluster.Name, compName)).GetObject() 331 model.NewGraphClient(nil).Root(dag, nil, root, nil) 332 component, err := components.NewComponent(ctx, cli, w.clusterDefObj, w.clusterVersionObj, cluster, compName, dag) 333 if err != nil { 334 return nil, nil, err 335 } 336 secret := factory.BuildConnCredential(w.clusterDefObj, cluster, component.GetSynthesizedComponent()) 337 cli.AppendMockObjects(secret) 338 if err = component.Create(ctx, cli); err != nil { 339 return nil, nil, err 340 } 341 return component.GetSynthesizedComponent(), objs, nil 342 }