github.com/oam-dev/kubevela@v1.9.11/pkg/appfile/template.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package appfile 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "strings" 24 25 "github.com/kubevela/pkg/multicluster" 26 "github.com/pkg/errors" 27 kerrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/api/meta" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 "k8s.io/apimachinery/pkg/runtime" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 34 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 35 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 36 "github.com/oam-dev/kubevela/apis/types" 37 oamutil "github.com/oam-dev/kubevela/pkg/oam/util" 38 ) 39 40 const ( 41 // UsageTag is usage comment annotation 42 UsageTag = "+usage=" 43 // ShortTag is the short alias annotation 44 ShortTag = "+short" 45 ) 46 47 // Template is a helper struct for processing capability including 48 // ComponentDefinition, TraitDefinition. 49 // It mainly collects schematic and status data of a capability definition. 50 type Template struct { 51 TemplateStr string 52 Health string 53 CustomStatus string 54 CapabilityCategory types.CapabilityCategory 55 Reference common.WorkloadTypeDescriptor 56 Terraform *common.Terraform 57 58 ComponentDefinition *v1beta1.ComponentDefinition 59 WorkloadDefinition *v1beta1.WorkloadDefinition 60 TraitDefinition *v1beta1.TraitDefinition 61 62 PolicyDefinition *v1beta1.PolicyDefinition 63 WorkflowStepDefinition *v1beta1.WorkflowStepDefinition 64 } 65 66 // LoadTemplate gets the capability definition from cluster and resolve it. 67 // It returns a helper struct, Template, which will be used for further 68 // processing. 69 func LoadTemplate(ctx context.Context, cli client.Client, capName string, capType types.CapType) (*Template, error) { 70 ctx = multicluster.WithCluster(ctx, multicluster.Local) 71 // Application Controller only loads template from ComponentDefinition and TraitDefinition 72 switch capType { 73 case types.TypeComponentDefinition, types.TypeWorkload: 74 cd := new(v1beta1.ComponentDefinition) 75 err := oamutil.GetCapabilityDefinition(ctx, cli, cd, capName) 76 if err != nil { 77 if kerrors.IsNotFound(err) { 78 wd := new(v1beta1.WorkloadDefinition) 79 if err := oamutil.GetDefinition(ctx, cli, wd, capName); err != nil { 80 return nil, errors.WithMessagef(err, "load template from component definition [%s] ", capName) 81 } 82 tmpl, err := newTemplateOfWorkloadDefinition(wd) 83 if err != nil { 84 return nil, err 85 } 86 gvk, err := oamutil.GetGVKFromDefinition(cli.RESTMapper(), wd.Spec.Reference) 87 if err != nil { 88 return nil, errors.WithMessagef(err, "get group version kind from component definition [%s]", capName) 89 } 90 tmpl.Reference = common.WorkloadTypeDescriptor{ 91 Definition: common.WorkloadGVK{ 92 APIVersion: metav1.GroupVersion{ 93 Group: gvk.Group, 94 Version: gvk.Version, 95 }.String(), 96 Kind: gvk.Kind, 97 }, 98 } 99 return tmpl, nil 100 } 101 return nil, errors.WithMessagef(err, "load template from component definition [%s] ", capName) 102 } 103 tmpl, err := newTemplateOfCompDefinition(cd) 104 if err != nil { 105 return nil, err 106 } 107 return tmpl, nil 108 109 case types.TypeTrait: 110 td := new(v1beta1.TraitDefinition) 111 err := oamutil.GetCapabilityDefinition(ctx, cli, td, capName) 112 if err != nil { 113 return nil, errors.WithMessagef(err, "load template from trait definition [%s] ", capName) 114 } 115 tmpl, err := newTemplateOfTraitDefinition(td) 116 if err != nil { 117 return nil, err 118 } 119 return tmpl, nil 120 case types.TypePolicy: 121 d := new(v1beta1.PolicyDefinition) 122 err := oamutil.GetCapabilityDefinition(ctx, cli, d, capName) 123 if err != nil { 124 return nil, errors.WithMessagef(err, "load template from policy definition [%s] ", capName) 125 } 126 tmpl, err := newTemplateOfPolicyDefinition(d) 127 if err != nil { 128 return nil, err 129 } 130 return tmpl, nil 131 case types.TypeWorkflowStep: 132 d := new(v1beta1.WorkflowStepDefinition) 133 err := oamutil.GetCapabilityDefinition(ctx, cli, d, capName) 134 if err != nil { 135 return nil, errors.WithMessagef(err, "load template from workflow step definition [%s] ", capName) 136 } 137 tmpl, err := newTemplateOfWorkflowStepDefinition(d) 138 if err != nil { 139 return nil, err 140 } 141 return tmpl, nil 142 } 143 return nil, fmt.Errorf("kind(%s) of %s not supported", capType, capName) 144 } 145 146 // LoadTemplateFromRevision will load Definition template from app revision 147 func LoadTemplateFromRevision(capName string, capType types.CapType, apprev *v1beta1.ApplicationRevision, mapper meta.RESTMapper) (*Template, error) { 148 if apprev == nil { 149 return nil, errors.Errorf("fail to find template for %s as app revision is empty", capName) 150 } 151 capName = verifyRevisionName(capName, capType, apprev) 152 switch capType { 153 case types.TypeComponentDefinition: 154 cd, ok := apprev.Spec.ComponentDefinitions[capName] 155 if !ok { 156 wd, ok := apprev.Spec.WorkloadDefinitions[capName] 157 if !ok { 158 return nil, errors.Errorf("component definition [%s] not found in app revision %s", capName, apprev.Name) 159 } 160 tmpl, err := newTemplateOfWorkloadDefinition(&wd) 161 if err != nil { 162 return nil, err 163 } 164 gvk, err := oamutil.GetGVKFromDefinition(mapper, wd.Spec.Reference) 165 if err != nil { 166 return nil, errors.WithMessagef(err, "Get group version kind from component definition [%s]", capName) 167 } 168 tmpl.Reference = common.WorkloadTypeDescriptor{ 169 Definition: common.WorkloadGVK{ 170 APIVersion: metav1.GroupVersion{ 171 Group: gvk.Group, 172 Version: gvk.Version, 173 }.String(), 174 Kind: gvk.Kind, 175 }, 176 } 177 return tmpl, nil 178 } 179 tmpl, err := newTemplateOfCompDefinition(cd.DeepCopy()) 180 if err != nil { 181 return nil, err 182 } 183 return tmpl, nil 184 185 case types.TypeTrait: 186 td, ok := apprev.Spec.TraitDefinitions[capName] 187 if !ok { 188 return nil, errors.Errorf("TraitDefinition [%s] not found in app revision %s", capName, apprev.Name) 189 } 190 tmpl, err := newTemplateOfTraitDefinition(td.DeepCopy()) 191 if err != nil { 192 return nil, err 193 } 194 return tmpl, nil 195 case types.TypePolicy: 196 d, ok := apprev.Spec.PolicyDefinitions[capName] 197 if !ok { 198 return nil, errors.Errorf("PolicyDefinition [%s] not found in app revision %s", capName, apprev.Name) 199 } 200 tmpl, err := newTemplateOfPolicyDefinition(d.DeepCopy()) 201 if err != nil { 202 return nil, err 203 } 204 return tmpl, nil 205 case types.TypeWorkflowStep: 206 w, ok := apprev.Spec.WorkflowStepDefinitions[capName] 207 if !ok { 208 return nil, errors.Errorf("WorkflowStepDefinition [%s] not found in app revision %s", capName, apprev.Name) 209 } 210 tmpl, err := newTemplateOfWorkflowStepDefinition(w.DeepCopy()) 211 if err != nil { 212 return nil, err 213 } 214 return tmpl, nil 215 default: 216 return nil, fmt.Errorf("kind(%s) of %s not supported", capType, capName) 217 } 218 } 219 220 // IsNotFoundInAppRevision check if the error is `not found in app revision` 221 func IsNotFoundInAppRevision(err error) bool { 222 return err != nil && strings.Contains(err.Error(), "not found in app revision") 223 } 224 225 func verifyRevisionName(capName string, capType types.CapType, apprev *v1beta1.ApplicationRevision) string { 226 if strings.Contains(capName, "@") { 227 splitName := capName[0:strings.LastIndex(capName, "@")] 228 ok := false 229 230 switch capType { 231 case types.TypeComponentDefinition: 232 _, ok = apprev.Spec.ComponentDefinitions[splitName] 233 case types.TypeTrait: 234 _, ok = apprev.Spec.TraitDefinitions[splitName] 235 case types.TypePolicy: 236 _, ok = apprev.Spec.PolicyDefinitions[splitName] 237 case types.TypeWorkflowStep: 238 _, ok = apprev.Spec.WorkflowStepDefinitions[splitName] 239 default: 240 return capName 241 } 242 243 if ok { 244 return splitName 245 } 246 } 247 248 return capName 249 } 250 251 // DryRunTemplateLoader return a function that do the same work as 252 // LoadTemplate, but load template from provided ones before loading from 253 // cluster through LoadTemplate 254 func DryRunTemplateLoader(defs []*unstructured.Unstructured) TemplateLoaderFn { 255 return func(ctx context.Context, r client.Client, capName string, capType types.CapType) (*Template, error) { 256 // retrieve provided cap definitions 257 for _, def := range defs { 258 if def.GetKind() == v1beta1.ComponentDefinitionKind && 259 capType == types.TypeComponentDefinition && def.GetName() == capName { 260 compDef := &v1beta1.ComponentDefinition{} 261 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(def.Object, compDef); err != nil { 262 return nil, errors.Wrap(err, "invalid component definition") 263 } 264 tmpl, err := newTemplateOfCompDefinition(compDef) 265 if err != nil { 266 return nil, errors.WithMessagef(err, "cannot load template of component definition %q", capName) 267 } 268 return tmpl, nil 269 } 270 if def.GetKind() == v1beta1.TraitDefinitionKind && 271 capType == types.TypeTrait && def.GetName() == capName { 272 traitDef := &v1beta1.TraitDefinition{} 273 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(def.Object, traitDef); err != nil { 274 return nil, errors.Wrap(err, "invalid trait definition") 275 } 276 tmpl, err := newTemplateOfTraitDefinition(traitDef) 277 if err != nil { 278 return nil, errors.WithMessagef(err, "cannot load template of trait definition %q", capName) 279 } 280 return tmpl, nil 281 } 282 } 283 // not found in provided cap definitions 284 // then try to retrieve from cluster 285 tmpl, err := LoadTemplate(ctx, r, capName, capType) 286 if err != nil { 287 return nil, errors.WithMessagef(err, "cannot load template %q from cluster and provided ones", capName) 288 } 289 return tmpl, nil 290 } 291 } 292 293 func newTemplateOfCompDefinition(compDef *v1beta1.ComponentDefinition) (*Template, error) { 294 tmpl := &Template{ 295 Reference: compDef.Spec.Workload, 296 ComponentDefinition: compDef, 297 } 298 if err := loadSchematicToTemplate(tmpl, compDef.Spec.Status, compDef.Spec.Schematic, compDef.Spec.Extension); err != nil { 299 return nil, errors.WithMessage(err, "cannot load template") 300 } 301 if compDef.Annotations["type"] == string(types.TerraformCategory) { 302 tmpl.CapabilityCategory = types.TerraformCategory 303 } 304 return tmpl, nil 305 } 306 307 func newTemplateOfTraitDefinition(traitDef *v1beta1.TraitDefinition) (*Template, error) { 308 tmpl := &Template{ 309 TraitDefinition: traitDef, 310 } 311 if err := loadSchematicToTemplate(tmpl, traitDef.Spec.Status, traitDef.Spec.Schematic, traitDef.Spec.Extension); err != nil { 312 return nil, errors.WithMessage(err, "cannot load template") 313 } 314 return tmpl, nil 315 } 316 317 func newTemplateOfWorkloadDefinition(wlDef *v1beta1.WorkloadDefinition) (*Template, error) { 318 tmpl := &Template{ 319 Reference: common.WorkloadTypeDescriptor{Type: wlDef.Spec.Reference.Name}, 320 WorkloadDefinition: wlDef, 321 } 322 if err := loadSchematicToTemplate(tmpl, wlDef.Spec.Status, wlDef.Spec.Schematic, wlDef.Spec.Extension); err != nil { 323 return nil, errors.WithMessage(err, "cannot load template") 324 } 325 return tmpl, nil 326 } 327 328 func newTemplateOfPolicyDefinition(def *v1beta1.PolicyDefinition) (*Template, error) { 329 tmpl := &Template{ 330 PolicyDefinition: def, 331 } 332 if err := loadSchematicToTemplate(tmpl, nil, def.Spec.Schematic, nil); err != nil { 333 return nil, errors.WithMessage(err, "cannot load template") 334 } 335 return tmpl, nil 336 } 337 338 func newTemplateOfWorkflowStepDefinition(def *v1beta1.WorkflowStepDefinition) (*Template, error) { 339 tmpl := &Template{ 340 WorkflowStepDefinition: def, 341 } 342 if err := loadSchematicToTemplate(tmpl, nil, def.Spec.Schematic, nil); err != nil { 343 return nil, errors.WithMessage(err, "cannot load template") 344 } 345 return tmpl, nil 346 } 347 348 // loadSchematicToTemplate loads common data that all kind definitions have. 349 func loadSchematicToTemplate(tmpl *Template, status *common.Status, schematic *common.Schematic, ext *runtime.RawExtension) error { 350 if status != nil { 351 tmpl.CustomStatus = status.CustomStatus 352 tmpl.Health = status.HealthPolicy 353 } 354 355 if schematic != nil { 356 if schematic.CUE != nil { 357 tmpl.CapabilityCategory = types.CUECategory 358 tmpl.TemplateStr = schematic.CUE.Template 359 } 360 if schematic.Terraform != nil { 361 tmpl.CapabilityCategory = types.TerraformCategory 362 tmpl.Terraform = schematic.Terraform 363 return nil 364 } 365 } 366 367 if tmpl.TemplateStr == "" && ext != nil { 368 tmpl.CapabilityCategory = types.CUECategory 369 extension := map[string]interface{}{} 370 if err := json.Unmarshal(ext.Raw, &extension); err != nil { 371 return errors.Wrap(err, "cannot parse capability extension") 372 } 373 if extTemplate, ok := extension["template"]; ok { 374 if tmpStr, ok := extTemplate.(string); ok { 375 tmpl.TemplateStr = tmpStr 376 } 377 } 378 } 379 return nil 380 } 381 382 // ConvertTemplateJSON2Object convert spec.extension or spec.schematic to object 383 func ConvertTemplateJSON2Object(capabilityName string, in *runtime.RawExtension, schematic *common.Schematic) (types.Capability, error) { 384 var t types.Capability 385 t.Name = capabilityName 386 if in != nil && in.Raw != nil { 387 err := json.Unmarshal(in.Raw, &t) 388 if err != nil { 389 return t, errors.Wrapf(err, "parse extension fail") 390 } 391 } 392 capTemplate := &Template{} 393 if err := loadSchematicToTemplate(capTemplate, nil, schematic, in); err != nil { 394 return t, errors.WithMessage(err, "cannot resolve schematic") 395 } 396 if capTemplate.TemplateStr != "" { 397 t.CueTemplate = capTemplate.TemplateStr 398 } 399 return t, nil 400 }