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  }