github.com/oam-dev/kubevela@v1.9.11/pkg/appfile/parser.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 "sort" 24 25 "github.com/pkg/errors" 26 kerrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime" 30 ktypes "k8s.io/apimachinery/pkg/types" 31 utilfeature "k8s.io/apiserver/pkg/util/feature" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 34 monitorContext "github.com/kubevela/pkg/monitor/context" 35 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 36 "github.com/kubevela/workflow/pkg/cue/packages" 37 38 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 39 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 40 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 41 "github.com/oam-dev/kubevela/apis/types" 42 "github.com/oam-dev/kubevela/pkg/auth" 43 "github.com/oam-dev/kubevela/pkg/component" 44 "github.com/oam-dev/kubevela/pkg/cue/definition" 45 "github.com/oam-dev/kubevela/pkg/features" 46 "github.com/oam-dev/kubevela/pkg/monitor/metrics" 47 "github.com/oam-dev/kubevela/pkg/oam" 48 "github.com/oam-dev/kubevela/pkg/oam/util" 49 policypkg "github.com/oam-dev/kubevela/pkg/policy" 50 "github.com/oam-dev/kubevela/pkg/utils" 51 utilscommon "github.com/oam-dev/kubevela/pkg/utils/common" 52 "github.com/oam-dev/kubevela/pkg/workflow/step" 53 ) 54 55 // TemplateLoaderFn load template of a capability definition 56 type TemplateLoaderFn func(context.Context, client.Client, string, types.CapType) (*Template, error) 57 58 // LoadTemplate load template of a capability definition 59 func (fn TemplateLoaderFn) LoadTemplate(ctx context.Context, c client.Client, capName string, capType types.CapType) (*Template, error) { 60 return fn(ctx, c, capName, capType) 61 } 62 63 // Parser is an application parser 64 type Parser struct { 65 client client.Client 66 pd *packages.PackageDiscover 67 tmplLoader TemplateLoaderFn 68 } 69 70 // NewApplicationParser create appfile parser 71 func NewApplicationParser(cli client.Client, pd *packages.PackageDiscover) *Parser { 72 return &Parser{ 73 client: cli, 74 pd: pd, 75 tmplLoader: LoadTemplate, 76 } 77 } 78 79 // NewDryRunApplicationParser create an appfile parser for DryRun 80 func NewDryRunApplicationParser(cli client.Client, pd *packages.PackageDiscover, defs []*unstructured.Unstructured) *Parser { 81 return &Parser{ 82 client: cli, 83 pd: pd, 84 tmplLoader: DryRunTemplateLoader(defs), 85 } 86 } 87 88 // GenerateAppFile generate appfile for the application to run, if the application is controlled by PublishVersion, 89 // the application revision will be used to create the appfile 90 func (p *Parser) GenerateAppFile(ctx context.Context, app *v1beta1.Application) (*Appfile, error) { 91 if ctx, ok := ctx.(monitorContext.Context); ok { 92 subCtx := ctx.Fork("generate-app-file", monitorContext.DurationMetric(func(v float64) { 93 metrics.AppReconcileStageDurationHistogram.WithLabelValues("generate-appfile").Observe(v) 94 })) 95 defer subCtx.Commit("finish generate appFile") 96 } 97 if isLatest, appRev, err := p.isLatestPublishVersion(ctx, app); err != nil { 98 return nil, err 99 } else if isLatest { 100 app.Spec = appRev.Spec.Application.Spec 101 return p.GenerateAppFileFromRevision(appRev) 102 } 103 return p.GenerateAppFileFromApp(ctx, app) 104 } 105 106 // GenerateAppFileFromApp converts an application to an Appfile 107 func (p *Parser) GenerateAppFileFromApp(ctx context.Context, app *v1beta1.Application) (*Appfile, error) { 108 109 for idx := range app.Spec.Policies { 110 if app.Spec.Policies[idx].Name == "" { 111 app.Spec.Policies[idx].Name = fmt.Sprintf("%s:auto-gen:%d", app.Spec.Policies[idx].Type, idx) 112 } 113 } 114 115 appFile := newAppFile(app) 116 if app.Status.LatestRevision != nil { 117 appFile.AppRevisionName = app.Status.LatestRevision.Name 118 } 119 120 var err error 121 if err = p.parseComponents(ctx, appFile); err != nil { 122 return nil, errors.Wrap(err, "failed to parseComponents") 123 } 124 if err = p.parseWorkflowSteps(ctx, appFile); err != nil { 125 return nil, errors.Wrap(err, "failed to parseWorkflowSteps") 126 } 127 if err = p.parsePolicies(ctx, appFile); err != nil { 128 return nil, errors.Wrap(err, "failed to parsePolicies") 129 } 130 if err = p.parseReferredObjects(ctx, appFile); err != nil { 131 return nil, errors.Wrap(err, "failed to parseReferredObjects") 132 } 133 134 return appFile, nil 135 } 136 137 func newAppFile(app *v1beta1.Application) *Appfile { 138 file := &Appfile{ 139 Name: app.Name, 140 Namespace: app.Namespace, 141 142 AppLabels: make(map[string]string), 143 AppAnnotations: make(map[string]string), 144 RelatedTraitDefinitions: make(map[string]*v1beta1.TraitDefinition), 145 RelatedComponentDefinitions: make(map[string]*v1beta1.ComponentDefinition), 146 RelatedWorkflowStepDefinitions: make(map[string]*v1beta1.WorkflowStepDefinition), 147 148 ExternalPolicies: make(map[string]*v1alpha1.Policy), 149 150 app: app, 151 } 152 for k, v := range app.Annotations { 153 file.AppAnnotations[k] = v 154 } 155 for k, v := range app.Labels { 156 file.AppLabels[k] = v 157 } 158 return file 159 } 160 161 // isLatestPublishVersion checks if the latest application revision has the same publishVersion with the application, 162 // return true and the latest ApplicationRevision if they share the same publishVersion 163 func (p *Parser) isLatestPublishVersion(ctx context.Context, app *v1beta1.Application) (bool, *v1beta1.ApplicationRevision, error) { 164 if !metav1.HasAnnotation(app.ObjectMeta, oam.AnnotationPublishVersion) { 165 return false, nil, nil 166 } 167 if app.Status.LatestRevision == nil { 168 return false, nil, nil 169 } 170 appRev := &v1beta1.ApplicationRevision{} 171 if err := p.client.Get(ctx, ktypes.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.GetNamespace()}, appRev); err != nil { 172 if kerrors.IsNotFound(err) { 173 return false, nil, nil 174 } 175 return false, nil, errors.Wrapf(err, "failed to load latest application revision") 176 } 177 if !metav1.HasAnnotation(appRev.ObjectMeta, oam.AnnotationPublishVersion) { 178 return false, nil, nil 179 } 180 if app.GetAnnotations()[oam.AnnotationPublishVersion] != appRev.GetAnnotations()[oam.AnnotationPublishVersion] { 181 return false, nil, nil 182 } 183 return true, appRev, nil 184 } 185 186 // inheritLabelAndAnnotationFromAppRev is a compatible function, that we can't record metadata for application object in AppRev 187 func inheritLabelAndAnnotationFromAppRev(appRev *v1beta1.ApplicationRevision) { 188 if len(appRev.Spec.Application.Annotations) > 0 || len(appRev.Spec.Application.Labels) > 0 { 189 return 190 } 191 appRev.Spec.Application.SetNamespace(appRev.Namespace) 192 if appRev.Spec.Application.GetName() == "" { 193 appRev.Spec.Application.SetName(appRev.Labels[oam.LabelAppName]) 194 } 195 labels := make(map[string]string) 196 for k, v := range appRev.GetLabels() { 197 if k == oam.LabelAppRevisionHash || k == oam.LabelAppName { 198 continue 199 } 200 labels[k] = v 201 } 202 appRev.Spec.Application.SetLabels(labels) 203 204 annotations := make(map[string]string) 205 for k, v := range appRev.GetAnnotations() { 206 annotations[k] = v 207 } 208 appRev.Spec.Application.SetAnnotations(annotations) 209 } 210 211 // GenerateAppFileFromRevision converts an application revision to an Appfile 212 func (p *Parser) GenerateAppFileFromRevision(appRev *v1beta1.ApplicationRevision) (*Appfile, error) { 213 214 inheritLabelAndAnnotationFromAppRev(appRev) 215 216 ctx := context.Background() 217 appfile := newAppFile(appRev.Spec.Application.DeepCopy()) 218 appfile.AppRevision = appRev 219 appfile.AppRevisionName = appRev.Name 220 appfile.AppRevisionHash = appRev.Labels[oam.LabelAppRevisionHash] 221 appfile.ExternalPolicies = make(map[string]*v1alpha1.Policy) 222 for key, po := range appRev.Spec.Policies { 223 appfile.ExternalPolicies[key] = po.DeepCopy() 224 } 225 appfile.ExternalWorkflow = appRev.Spec.Workflow 226 227 if err := p.parseComponentsFromRevision(appfile); err != nil { 228 return nil, errors.Wrap(err, "failed to parseComponentsFromRevision") 229 } 230 if err := p.parseWorkflowStepsFromRevision(ctx, appfile); err != nil { 231 return nil, errors.Wrap(err, "failed to parseWorkflowStepsFromRevision") 232 } 233 if err := p.parsePoliciesFromRevision(ctx, appfile); err != nil { 234 return nil, errors.Wrap(err, "failed to parsePolicies") 235 } 236 if err := p.parseReferredObjectsFromRevision(appfile); err != nil { 237 return nil, errors.Wrap(err, "failed to parseReferredObjects") 238 } 239 240 // add compatible code for upgrading to v1.3 as the workflow steps were not recorded before v1.2 241 if len(appfile.RelatedWorkflowStepDefinitions) == 0 && len(appfile.WorkflowSteps) > 0 { 242 if err := p.parseWorkflowStepsForLegacyRevision(ctx, appfile); err != nil { 243 return nil, errors.Wrap(err, "failed to parseWorkflowStepsForLegacyRevision") 244 } 245 } 246 247 return appfile, nil 248 } 249 250 // parseWorkflowStepsForLegacyRevision compatible for upgrading to v1.3 as the workflow steps were not recorded before v1.2 251 func (p *Parser) parseWorkflowStepsForLegacyRevision(ctx context.Context, af *Appfile) error { 252 for _, workflowStep := range af.WorkflowSteps { 253 if step.IsBuiltinWorkflowStepType(workflowStep.Type) { 254 continue 255 } 256 if _, found := af.RelatedWorkflowStepDefinitions[workflowStep.Type]; found { 257 continue 258 } 259 def := &v1beta1.WorkflowStepDefinition{} 260 if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStep.Type); err != nil { 261 return errors.Wrapf(err, "failed to get workflow step definition %s", workflowStep.Type) 262 } 263 af.RelatedWorkflowStepDefinitions[workflowStep.Type] = def 264 } 265 266 af.AppRevision.Spec.WorkflowStepDefinitions = make(map[string]*v1beta1.WorkflowStepDefinition) 267 for name, def := range af.RelatedWorkflowStepDefinitions { 268 af.AppRevision.Spec.WorkflowStepDefinitions[name] = def 269 } 270 return nil 271 } 272 273 func (p *Parser) parseReferredObjectsFromRevision(af *Appfile) error { 274 af.ReferredObjects = []*unstructured.Unstructured{} 275 for _, obj := range af.AppRevision.Spec.ReferredObjects { 276 un := &unstructured.Unstructured{} 277 if err := json.Unmarshal(obj.Raw, un); err != nil { 278 return errors.Errorf("failed to unmarshal referred objects %s", obj.Raw) 279 } 280 af.ReferredObjects = append(af.ReferredObjects, un) 281 } 282 return nil 283 } 284 285 func (p *Parser) parseReferredObjects(ctx context.Context, af *Appfile) error { 286 ctx = auth.ContextWithUserInfo(ctx, af.app) 287 for _, comp := range af.Components { 288 if comp.Type != v1alpha1.RefObjectsComponentType { 289 continue 290 } 291 spec := &v1alpha1.RefObjectsComponentSpec{} 292 if err := utils.StrictUnmarshal(comp.Properties.Raw, spec); err != nil { 293 return errors.Wrapf(err, "invalid properties for ref-objects in component %s", comp.Name) 294 } 295 for _, selector := range spec.Objects { 296 objs, err := component.SelectRefObjectsForDispatch(ctx, p.client, af.app.GetNamespace(), comp.Name, selector) 297 if err != nil { 298 return err 299 } 300 af.ReferredObjects = component.AppendUnstructuredObjects(af.ReferredObjects, objs...) 301 } 302 if utilfeature.DefaultMutableFeatureGate.Enabled(features.DisableReferObjectsFromURL) && len(spec.URLs) > 0 { 303 return fmt.Errorf("referring objects from url is disabled") 304 } 305 for _, url := range spec.URLs { 306 objs, err := utilscommon.HTTPGetKubernetesObjects(ctx, url) 307 if err != nil { 308 return fmt.Errorf("failed to load Kubernetes objects from url %s: %w", url, err) 309 } 310 for _, obj := range objs { 311 util.AddAnnotations(obj, map[string]string{oam.AnnotationResourceURL: url}) 312 } 313 af.ReferredObjects = component.AppendUnstructuredObjects(af.ReferredObjects, objs...) 314 } 315 } 316 sort.Slice(af.ReferredObjects, func(i, j int) bool { 317 a, b := af.ReferredObjects[i], af.ReferredObjects[j] 318 keyA := a.GroupVersionKind().String() + "|" + client.ObjectKeyFromObject(a).String() 319 keyB := b.GroupVersionKind().String() + "|" + client.ObjectKeyFromObject(b).String() 320 return keyA < keyB 321 }) 322 return nil 323 } 324 325 func (p *Parser) parsePoliciesFromRevision(ctx context.Context, af *Appfile) (err error) { 326 af.Policies, err = step.LoadExternalPoliciesForWorkflow(ctx, af.PolicyClient(p.client), af.app.GetNamespace(), af.WorkflowSteps, af.app.Spec.Policies) 327 if err != nil { 328 return err 329 } 330 for _, policy := range af.Policies { 331 if policy.Properties == nil && policy.Type != v1alpha1.DebugPolicyType { 332 return fmt.Errorf("policy %s named %s must not have empty properties", policy.Type, policy.Name) 333 } 334 switch policy.Type { 335 case v1alpha1.GarbageCollectPolicyType: 336 case v1alpha1.ApplyOncePolicyType: 337 case v1alpha1.SharedResourcePolicyType: 338 case v1alpha1.TakeOverPolicyType: 339 case v1alpha1.ReadOnlyPolicyType: 340 case v1alpha1.ResourceUpdatePolicyType: 341 case v1alpha1.EnvBindingPolicyType: 342 case v1alpha1.TopologyPolicyType: 343 case v1alpha1.OverridePolicyType: 344 case v1alpha1.DebugPolicyType: 345 af.Debug = true 346 default: 347 w, err := p.makeComponentFromRevision(policy.Name, policy.Type, types.TypePolicy, policy.Properties, af.AppRevision) 348 if err != nil { 349 return err 350 } 351 af.ParsedPolicies = append(af.ParsedPolicies, w) 352 } 353 } 354 return nil 355 } 356 357 func (p *Parser) parsePolicies(ctx context.Context, af *Appfile) (err error) { 358 af.Policies, err = step.LoadExternalPoliciesForWorkflow(ctx, af.PolicyClient(p.client), af.app.GetNamespace(), af.WorkflowSteps, af.app.Spec.Policies) 359 if err != nil { 360 return err 361 } 362 for _, policy := range af.Policies { 363 if policy.Properties == nil && policy.Type != v1alpha1.DebugPolicyType { 364 return fmt.Errorf("policy %s named %s must not have empty properties", policy.Type, policy.Name) 365 } 366 switch policy.Type { 367 case v1alpha1.GarbageCollectPolicyType: 368 case v1alpha1.ApplyOncePolicyType: 369 case v1alpha1.SharedResourcePolicyType: 370 case v1alpha1.TakeOverPolicyType: 371 case v1alpha1.ReadOnlyPolicyType: 372 case v1alpha1.ResourceUpdatePolicyType: 373 case v1alpha1.EnvBindingPolicyType: 374 case v1alpha1.TopologyPolicyType: 375 case v1alpha1.ReplicationPolicyType: 376 case v1alpha1.DebugPolicyType: 377 af.Debug = true 378 case v1alpha1.OverridePolicyType: 379 compDefs, traitDefs, err := policypkg.ParseOverridePolicyRelatedDefinitions(ctx, p.client, af.app, policy) 380 if err != nil { 381 return err 382 } 383 for _, def := range compDefs { 384 af.RelatedComponentDefinitions[def.Name] = def 385 } 386 for _, def := range traitDefs { 387 af.RelatedTraitDefinitions[def.Name] = def 388 } 389 default: 390 w, err := p.makeComponent(ctx, policy.Name, policy.Type, types.TypePolicy, policy.Properties) 391 if err != nil { 392 return err 393 } 394 af.ParsedPolicies = append(af.ParsedPolicies, w) 395 } 396 } 397 return nil 398 } 399 400 func (p *Parser) loadWorkflowToAppfile(ctx context.Context, af *Appfile) error { 401 var err error 402 // parse workflow steps 403 af.WorkflowMode = &workflowv1alpha1.WorkflowExecuteMode{ 404 Steps: workflowv1alpha1.WorkflowModeDAG, 405 SubSteps: workflowv1alpha1.WorkflowModeDAG, 406 } 407 if wfSpec := af.app.Spec.Workflow; wfSpec != nil { 408 app := af.app 409 mode := wfSpec.Mode 410 if wfSpec.Ref != "" && mode == nil { 411 wf := &workflowv1alpha1.Workflow{} 412 if err := af.WorkflowClient(p.client).Get(ctx, ktypes.NamespacedName{Namespace: af.app.Namespace, Name: app.Spec.Workflow.Ref}, wf); err != nil { 413 return err 414 } 415 mode = wf.Mode 416 } 417 af.WorkflowSteps = wfSpec.Steps 418 af.WorkflowMode.Steps = workflowv1alpha1.WorkflowModeStep 419 if mode != nil { 420 if mode.Steps != "" { 421 af.WorkflowMode.Steps = mode.Steps 422 } 423 if mode.SubSteps != "" { 424 af.WorkflowMode.SubSteps = mode.SubSteps 425 } 426 } 427 } 428 af.WorkflowSteps, err = step.NewChainWorkflowStepGenerator( 429 &step.RefWorkflowStepGenerator{Client: af.WorkflowClient(p.client), Context: ctx}, 430 &step.DeployWorkflowStepGenerator{}, 431 &step.Deploy2EnvWorkflowStepGenerator{}, 432 &step.ApplyComponentWorkflowStepGenerator{}, 433 ).Generate(af.app, af.WorkflowSteps) 434 return err 435 } 436 437 func (p *Parser) parseWorkflowStepsFromRevision(ctx context.Context, af *Appfile) error { 438 if err := p.loadWorkflowToAppfile(ctx, af); err != nil { 439 return err 440 } 441 // Definitions are already in AppRevision 442 for k, v := range af.AppRevision.Spec.WorkflowStepDefinitions { 443 af.RelatedWorkflowStepDefinitions[k] = v.DeepCopy() 444 } 445 return nil 446 } 447 448 func (p *Parser) parseWorkflowSteps(ctx context.Context, af *Appfile) error { 449 if err := p.loadWorkflowToAppfile(ctx, af); err != nil { 450 return err 451 } 452 for _, workflowStep := range af.WorkflowSteps { 453 err := p.fetchAndSetWorkflowStepDefinition(ctx, af, workflowStep.Type) 454 if err != nil { 455 return err 456 } 457 458 if workflowStep.SubSteps != nil { 459 for _, workflowSubStep := range workflowStep.SubSteps { 460 err := p.fetchAndSetWorkflowStepDefinition(ctx, af, workflowSubStep.Type) 461 if err != nil { 462 return err 463 } 464 } 465 } 466 } 467 return nil 468 } 469 470 func (p *Parser) fetchAndSetWorkflowStepDefinition(ctx context.Context, af *Appfile, workflowStepType string) error { 471 if step.IsBuiltinWorkflowStepType(workflowStepType) { 472 return nil 473 } 474 if _, found := af.RelatedWorkflowStepDefinitions[workflowStepType]; found { 475 return nil 476 } 477 def := &v1beta1.WorkflowStepDefinition{} 478 if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStepType); err != nil { 479 return errors.Wrapf(err, "failed to get workflow step definition %s", workflowStepType) 480 } 481 af.RelatedWorkflowStepDefinitions[workflowStepType] = def 482 return nil 483 } 484 485 func (p *Parser) makeComponent(ctx context.Context, name, typ string, capType types.CapType, props *runtime.RawExtension) (*Component, error) { 486 templ, err := p.tmplLoader.LoadTemplate(ctx, p.client, typ, capType) 487 if err != nil { 488 return nil, errors.WithMessagef(err, "fetch component/policy type of %s", name) 489 } 490 return p.convertTemplate2Component(name, typ, props, templ) 491 } 492 493 func (p *Parser) makeComponentFromRevision(name, typ string, capType types.CapType, props *runtime.RawExtension, appRev *v1beta1.ApplicationRevision) (*Component, error) { 494 templ, err := LoadTemplateFromRevision(typ, capType, appRev, p.client.RESTMapper()) 495 if err != nil { 496 return nil, errors.WithMessagef(err, "fetch component/policy type of %s from revision", name) 497 } 498 499 return p.convertTemplate2Component(name, typ, props, templ) 500 } 501 502 func (p *Parser) convertTemplate2Component(name, typ string, props *runtime.RawExtension, templ *Template) (*Component, error) { 503 settings, err := util.RawExtension2Map(props) 504 if err != nil { 505 return nil, errors.WithMessagef(err, "fail to parse settings for %s", name) 506 } 507 cpType, err := util.ConvertDefinitionRevName(typ) 508 if err != nil { 509 cpType = typ 510 } 511 return &Component{ 512 Traits: []*Trait{}, 513 Name: name, 514 Type: cpType, 515 CapabilityCategory: templ.CapabilityCategory, 516 FullTemplate: templ, 517 Params: settings, 518 engine: definition.NewWorkloadAbstractEngine(name, p.pd), 519 }, nil 520 } 521 522 // parseComponents resolve an Application Components and Traits to generate Component 523 func (p *Parser) parseComponents(ctx context.Context, af *Appfile) error { 524 var comps []*Component 525 for _, c := range af.app.Spec.Components { 526 comp, err := p.parseComponent(ctx, c) 527 if err != nil { 528 return err 529 } 530 comps = append(comps, comp) 531 } 532 533 af.ParsedComponents = comps 534 af.Components = af.app.Spec.Components 535 setComponentDefinitions(af, comps) 536 537 return nil 538 } 539 540 func setComponentDefinitions(af *Appfile, comps []*Component) { 541 for _, comp := range comps { 542 if comp == nil { 543 continue 544 } 545 if comp.FullTemplate.ComponentDefinition != nil { 546 cd := comp.FullTemplate.ComponentDefinition.DeepCopy() 547 cd.Status = v1beta1.ComponentDefinitionStatus{} 548 af.RelatedComponentDefinitions[comp.FullTemplate.ComponentDefinition.Name] = cd 549 } 550 for _, t := range comp.Traits { 551 if t == nil { 552 continue 553 } 554 if t.FullTemplate.TraitDefinition != nil { 555 td := t.FullTemplate.TraitDefinition.DeepCopy() 556 td.Status = v1beta1.TraitDefinitionStatus{} 557 af.RelatedTraitDefinitions[t.FullTemplate.TraitDefinition.Name] = td 558 } 559 } 560 } 561 } 562 563 // setComponentDefinitionsFromRevision can set related definitions directly from app revision 564 func setComponentDefinitionsFromRevision(af *Appfile) { 565 for k, v := range af.AppRevision.Spec.ComponentDefinitions { 566 af.RelatedComponentDefinitions[k] = v.DeepCopy() 567 } 568 for k, v := range af.AppRevision.Spec.TraitDefinitions { 569 af.RelatedTraitDefinitions[k] = v.DeepCopy() 570 } 571 } 572 573 // parseComponent resolve an ApplicationComponent and generate a Component 574 // containing ALL information required by an Appfile. 575 func (p *Parser) parseComponent(ctx context.Context, comp common.ApplicationComponent) (*Component, error) { 576 workload, err := p.makeComponent(ctx, comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties) 577 if err != nil { 578 return nil, err 579 } 580 581 if err = p.parseTraits(ctx, workload, comp); err != nil { 582 return nil, err 583 } 584 return workload, nil 585 } 586 587 func (p *Parser) parseTraits(ctx context.Context, workload *Component, comp common.ApplicationComponent) error { 588 for _, traitValue := range comp.Traits { 589 properties, err := util.RawExtension2Map(traitValue.Properties) 590 if err != nil { 591 return errors.Errorf("fail to parse properties of %s for %s", traitValue.Type, comp.Name) 592 } 593 trait, err := p.parseTrait(ctx, traitValue.Type, properties) 594 if err != nil { 595 return errors.WithMessagef(err, "component(%s) parse trait(%s)", comp.Name, traitValue.Type) 596 } 597 598 workload.Traits = append(workload.Traits, trait) 599 } 600 return nil 601 } 602 603 func (p *Parser) parseComponentsFromRevision(af *Appfile) error { 604 var comps []*Component 605 for _, c := range af.app.Spec.Components { 606 comp, err := p.ParseComponentFromRevision(c, af.AppRevision) 607 if err != nil { 608 return err 609 } 610 comps = append(comps, comp) 611 } 612 af.ParsedComponents = comps 613 af.Components = af.app.Spec.Components 614 // Definitions are already in AppRevision 615 setComponentDefinitionsFromRevision(af) 616 return nil 617 } 618 619 // ParseComponentFromRevision resolve an ApplicationComponent and generate a Component 620 // containing ALL information required by an Appfile from app revision. 621 func (p *Parser) ParseComponentFromRevision(comp common.ApplicationComponent, appRev *v1beta1.ApplicationRevision) (*Component, error) { 622 workload, err := p.makeComponentFromRevision(comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties, appRev) 623 if err != nil { 624 return nil, err 625 } 626 627 if err = p.parseTraitsFromRevision(comp, appRev, workload); err != nil { 628 return nil, err 629 } 630 631 return workload, nil 632 } 633 634 func (p *Parser) parseTraitsFromRevision(comp common.ApplicationComponent, appRev *v1beta1.ApplicationRevision, workload *Component) error { 635 for _, traitValue := range comp.Traits { 636 properties, err := util.RawExtension2Map(traitValue.Properties) 637 if err != nil { 638 return errors.Errorf("fail to parse properties of %s for %s", traitValue.Type, comp.Name) 639 } 640 trait, err := p.parseTraitFromRevision(traitValue.Type, properties, appRev) 641 if err != nil { 642 return errors.WithMessagef(err, "component(%s) parse trait(%s)", comp.Name, traitValue.Type) 643 } 644 645 workload.Traits = append(workload.Traits, trait) 646 } 647 return nil 648 } 649 650 // ParseComponentFromRevisionAndClient resolve an ApplicationComponent and generate a Component 651 // containing ALL information required by an Appfile from app revision, and will fall back to 652 // load external definitions if not found 653 func (p *Parser) ParseComponentFromRevisionAndClient(ctx context.Context, c common.ApplicationComponent, appRev *v1beta1.ApplicationRevision) (*Component, error) { 654 comp, err := p.makeComponentFromRevision(c.Name, c.Type, types.TypeComponentDefinition, c.Properties, appRev) 655 if IsNotFoundInAppRevision(err) { 656 comp, err = p.makeComponent(ctx, c.Name, c.Type, types.TypeComponentDefinition, c.Properties) 657 } 658 if err != nil { 659 return nil, err 660 } 661 662 for _, traitValue := range c.Traits { 663 properties, err := util.RawExtension2Map(traitValue.Properties) 664 if err != nil { 665 return nil, errors.Errorf("fail to parse properties of %s for %s", traitValue.Type, c.Name) 666 } 667 trait, err := p.parseTraitFromRevision(traitValue.Type, properties, appRev) 668 if IsNotFoundInAppRevision(err) { 669 trait, err = p.parseTrait(ctx, traitValue.Type, properties) 670 } 671 if err != nil { 672 return nil, errors.WithMessagef(err, "component(%s) parse trait(%s)", c.Name, traitValue.Type) 673 } 674 675 comp.Traits = append(comp.Traits, trait) 676 } 677 678 return comp, nil 679 } 680 681 func (p *Parser) parseTrait(ctx context.Context, name string, properties map[string]interface{}) (*Trait, error) { 682 templ, err := p.tmplLoader.LoadTemplate(ctx, p.client, name, types.TypeTrait) 683 if kerrors.IsNotFound(err) { 684 return nil, errors.Errorf("trait definition of %s not found", name) 685 } 686 if err != nil { 687 return nil, err 688 } 689 return p.convertTemplate2Trait(name, properties, templ) 690 } 691 692 func (p *Parser) parseTraitFromRevision(name string, properties map[string]interface{}, appRev *v1beta1.ApplicationRevision) (*Trait, error) { 693 templ, err := LoadTemplateFromRevision(name, types.TypeTrait, appRev, p.client.RESTMapper()) 694 if err != nil { 695 return nil, err 696 } 697 return p.convertTemplate2Trait(name, properties, templ) 698 } 699 700 func (p *Parser) convertTemplate2Trait(name string, properties map[string]interface{}, templ *Template) (*Trait, error) { 701 traitName, err := util.ConvertDefinitionRevName(name) 702 if err != nil { 703 traitName = name 704 } 705 return &Trait{ 706 Name: traitName, 707 CapabilityCategory: templ.CapabilityCategory, 708 Params: properties, 709 Template: templ.TemplateStr, 710 HealthCheckPolicy: templ.Health, 711 CustomStatusFormat: templ.CustomStatus, 712 FullTemplate: templ, 713 engine: definition.NewTraitAbstractEngine(traitName, p.pd), 714 }, nil 715 } 716 717 // ValidateComponentNames validate all component names whether repeat in app 718 func (p *Parser) ValidateComponentNames(app *v1beta1.Application) (int, error) { 719 compNames := map[string]struct{}{} 720 for idx, comp := range app.Spec.Components { 721 if _, found := compNames[comp.Name]; found { 722 return idx, fmt.Errorf("duplicated component name %s", comp.Name) 723 } 724 compNames[comp.Name] = struct{}{} 725 } 726 return 0, nil 727 }