github.com/oam-dev/kubevela@v1.9.11/pkg/appfile/appfile.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  	"reflect"
    24  	"strings"
    25  
    26  	"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
    27  	"github.com/kubevela/pkg/util/slices"
    28  	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
    29  	"github.com/pkg/errors"
    30  	corev1 "k8s.io/api/core/v1"
    31  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  
    37  	velaclient "github.com/kubevela/pkg/controller/client"
    38  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    39  	"github.com/kubevela/workflow/pkg/cue/model/value"
    40  	"github.com/kubevela/workflow/pkg/cue/process"
    41  
    42  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    43  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    44  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    45  	"github.com/oam-dev/kubevela/apis/types"
    46  	"github.com/oam-dev/kubevela/pkg/auth"
    47  	"github.com/oam-dev/kubevela/pkg/component"
    48  	"github.com/oam-dev/kubevela/pkg/cue/definition"
    49  	velaprocess "github.com/oam-dev/kubevela/pkg/cue/process"
    50  	"github.com/oam-dev/kubevela/pkg/oam"
    51  	"github.com/oam-dev/kubevela/pkg/oam/util"
    52  )
    53  
    54  // constant error information
    55  const (
    56  	errInvalidValueType                          = "require %q type parameter value"
    57  	errTerraformConfigurationIsNotSet            = "terraform configuration is not set"
    58  	errTerraformComponentDefinition              = "terraform component definition is not valid"
    59  	errFailToConvertTerraformComponentProperties = "failed to convert Terraform component properties"
    60  )
    61  
    62  const (
    63  	// WriteConnectionSecretToRefKey is used to create a secret for cloud resource connection
    64  	WriteConnectionSecretToRefKey = "writeConnectionSecretToRef"
    65  	// RegionKey is the region of a Cloud Provider
    66  	// It's used to override the region of a Cloud Provider
    67  	// Refer to https://github.com/oam-dev/terraform-controller/blob/master/api/v1beta2/configuration_types.go#L66 for details
    68  	RegionKey = "customRegion"
    69  	// ProviderRefKey is the reference of a Provider
    70  	ProviderRefKey = "providerRef"
    71  	// ForceDeleteKey is used to force delete Configuration
    72  	ForceDeleteKey = "forceDelete"
    73  	// GitCredentialsSecretReferenceKey is the reference to a secret with git ssh private key & known hosts
    74  	GitCredentialsSecretReferenceKey = "gitCredentialsSecretReference"
    75  )
    76  
    77  // Component is an internal struct for component in application
    78  // User-defined policies are parsed as a Component without any Traits because their purpose is dispatching some resources
    79  // Internal policies are NOT parsed as a Component
    80  type Component struct {
    81  	Name               string
    82  	Type               string
    83  	CapabilityCategory types.CapabilityCategory
    84  	Params             map[string]interface{}
    85  	Traits             []*Trait
    86  	FullTemplate       *Template
    87  	Ctx                process.Context
    88  	Patch              *value.Value
    89  	engine             definition.AbstractEngine
    90  	SkipApplyWorkload  bool
    91  }
    92  
    93  // EvalContext eval workload template and set the result to context
    94  func (comp *Component) EvalContext(ctx process.Context) error {
    95  	return comp.engine.Complete(ctx, comp.FullTemplate.TemplateStr, comp.Params)
    96  }
    97  
    98  // GetTemplateContext get workload template context, it will be used to eval status and health
    99  func (comp *Component) GetTemplateContext(ctx process.Context, client client.Client, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
   100  	// if the standard workload is managed by trait, just return empty context
   101  	if comp.SkipApplyWorkload {
   102  		return nil, nil
   103  	}
   104  	templateContext, err := comp.engine.GetTemplateContext(ctx, client, accessor)
   105  	if templateContext != nil {
   106  		templateContext[velaprocess.ParameterFieldName] = comp.Params
   107  	}
   108  	return templateContext, err
   109  }
   110  
   111  // EvalStatus eval workload status
   112  func (comp *Component) EvalStatus(templateContext map[string]interface{}) (string, error) {
   113  	// if the standard workload is managed by trait always return empty message
   114  	if comp.SkipApplyWorkload {
   115  		return "", nil
   116  	}
   117  	return comp.engine.Status(templateContext, comp.FullTemplate.CustomStatus, comp.Params)
   118  }
   119  
   120  // EvalHealth eval workload health check
   121  func (comp *Component) EvalHealth(templateContext map[string]interface{}) (bool, error) {
   122  	// if the health of template is not set or standard workload is managed by trait always return true
   123  	if comp.SkipApplyWorkload {
   124  		return true, nil
   125  	}
   126  	return comp.engine.HealthCheck(templateContext, comp.FullTemplate.Health, comp.Params)
   127  }
   128  
   129  // Trait is ComponentTrait
   130  type Trait struct {
   131  	// The Name is name of TraitDefinition, actually it's a type of the trait instance
   132  	Name               string
   133  	CapabilityCategory types.CapabilityCategory
   134  	Params             map[string]interface{}
   135  
   136  	Template           string
   137  	HealthCheckPolicy  string
   138  	CustomStatusFormat string
   139  
   140  	// RequiredSecrets stores secret names which the trait needs from cloud resource component and its context
   141  	RequiredSecrets []process.RequiredSecrets
   142  
   143  	FullTemplate *Template
   144  	engine       definition.AbstractEngine
   145  }
   146  
   147  // EvalContext eval trait template and set result to context
   148  func (trait *Trait) EvalContext(ctx process.Context) error {
   149  	return trait.engine.Complete(ctx, trait.Template, trait.Params)
   150  }
   151  
   152  // GetTemplateContext get trait template context, it will be used to eval status and health
   153  func (trait *Trait) GetTemplateContext(ctx process.Context, client client.Client, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
   154  	templateContext, err := trait.engine.GetTemplateContext(ctx, client, accessor)
   155  	if templateContext != nil {
   156  		templateContext[velaprocess.ParameterFieldName] = trait.Params
   157  	}
   158  	return templateContext, err
   159  }
   160  
   161  // EvalStatus eval trait status
   162  func (trait *Trait) EvalStatus(templateContext map[string]interface{}) (string, error) {
   163  	return trait.engine.Status(templateContext, trait.CustomStatusFormat, trait.Params)
   164  }
   165  
   166  // EvalHealth eval trait health check
   167  func (trait *Trait) EvalHealth(templateContext map[string]interface{}) (bool, error) {
   168  	return trait.engine.HealthCheck(templateContext, trait.HealthCheckPolicy, trait.Params)
   169  }
   170  
   171  // Appfile describes application
   172  type Appfile struct {
   173  	Name             string
   174  	Namespace        string
   175  	ParsedComponents []*Component
   176  	ParsedPolicies   []*Component
   177  
   178  	AppRevision     *v1beta1.ApplicationRevision
   179  	AppRevisionName string
   180  	AppRevisionHash string
   181  
   182  	AppLabels      map[string]string
   183  	AppAnnotations map[string]string
   184  
   185  	RelatedTraitDefinitions        map[string]*v1beta1.TraitDefinition
   186  	RelatedComponentDefinitions    map[string]*v1beta1.ComponentDefinition
   187  	RelatedWorkflowStepDefinitions map[string]*v1beta1.WorkflowStepDefinition
   188  
   189  	Policies      []v1beta1.AppPolicy
   190  	Components    []common.ApplicationComponent
   191  	Artifacts     []*types.ComponentManifest
   192  	WorkflowSteps []workflowv1alpha1.WorkflowStep
   193  	WorkflowMode  *workflowv1alpha1.WorkflowExecuteMode
   194  
   195  	ExternalPolicies map[string]*v1alpha1.Policy
   196  	ExternalWorkflow *workflowv1alpha1.Workflow
   197  	ReferredObjects  []*unstructured.Unstructured
   198  
   199  	app *v1beta1.Application
   200  
   201  	Debug bool
   202  }
   203  
   204  // GeneratePolicyManifests generates policy manifests from an appFile
   205  // internal policies like apply-once, topology, will not render manifests
   206  func (af *Appfile) GeneratePolicyManifests(_ context.Context) ([]*unstructured.Unstructured, error) {
   207  	var manifests []*unstructured.Unstructured
   208  	for _, policy := range af.ParsedPolicies {
   209  		un, err := af.generatePolicyUnstructured(policy)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		manifests = append(manifests, un...)
   214  	}
   215  	return manifests, nil
   216  }
   217  
   218  func (af *Appfile) generatePolicyUnstructured(workload *Component) ([]*unstructured.Unstructured, error) {
   219  	ctxData := GenerateContextDataFromAppFile(af, workload.Name)
   220  	uns, err := generatePolicyUnstructuredFromCUEModule(workload, af.Artifacts, ctxData)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	for _, un := range uns {
   225  		if len(un.GetName()) == 0 {
   226  			un.SetName(workload.Name)
   227  		}
   228  		if len(un.GetNamespace()) == 0 {
   229  			un.SetNamespace(af.Namespace)
   230  		}
   231  	}
   232  	return uns, nil
   233  }
   234  
   235  func generatePolicyUnstructuredFromCUEModule(comp *Component, artifacts []*types.ComponentManifest, ctxData velaprocess.ContextData) ([]*unstructured.Unstructured, error) {
   236  	pCtx := velaprocess.NewContext(ctxData)
   237  	pCtx.PushData(velaprocess.ContextDataArtifacts, prepareArtifactsData(artifacts))
   238  	if err := comp.EvalContext(pCtx); err != nil {
   239  		return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", ctxData.AppName, ctxData.Namespace)
   240  	}
   241  	base, auxs := pCtx.Output()
   242  	workload, err := base.Unstructured()
   243  	if err != nil {
   244  		return nil, errors.Wrapf(err, "evaluate base template policy=%s app=%s", comp.Name, ctxData.AppName)
   245  	}
   246  	commonLabels := definition.GetCommonLabels(definition.GetBaseContextLabels(pCtx))
   247  	util.AddLabels(workload, commonLabels)
   248  
   249  	var res = []*unstructured.Unstructured{workload}
   250  	for _, assist := range auxs {
   251  		tr, err := assist.Ins.Unstructured()
   252  		if err != nil {
   253  			return nil, errors.Wrapf(err, "evaluate auxiliary=%s template for policy=%s app=%s", assist.Name, comp.Name, ctxData.AppName)
   254  		}
   255  		util.AddLabels(tr, commonLabels)
   256  		res = append(res, tr)
   257  	}
   258  	return res, nil
   259  }
   260  
   261  // artifacts contains resources in unstructured shape of all components
   262  // it allows to access values of workloads and traits in CUE template, i.g.,
   263  // `if context.artifacts.<compName>.ready` to determine whether it's ready to access
   264  // `context.artifacts.<compName>.workload` to access a workload
   265  // `context.artifacts.<compName>.traits.<traitType>.<traitResource>` to access a trait
   266  func prepareArtifactsData(comps []*types.ComponentManifest) map[string]interface{} {
   267  	artifacts := unstructured.Unstructured{Object: make(map[string]interface{})}
   268  	for _, pComp := range comps {
   269  		if pComp.ComponentOutput != nil {
   270  			_ = unstructured.SetNestedField(artifacts.Object, pComp.ComponentOutput.Object, pComp.Name, "workload")
   271  		}
   272  		for _, t := range pComp.ComponentOutputsAndTraits {
   273  			if t == nil {
   274  				continue
   275  			}
   276  			_ = unstructured.SetNestedField(artifacts.Object, t.Object, pComp.Name,
   277  				"traits",
   278  				t.GetLabels()[oam.TraitTypeLabel],
   279  				t.GetLabels()[oam.TraitResource])
   280  		}
   281  	}
   282  	return artifacts.Object
   283  }
   284  
   285  // GenerateComponentManifests converts an appFile to a slice of ComponentManifest
   286  func (af *Appfile) GenerateComponentManifests() ([]*types.ComponentManifest, error) {
   287  	compManifests := make([]*types.ComponentManifest, len(af.ParsedComponents))
   288  	af.Artifacts = make([]*types.ComponentManifest, len(af.ParsedComponents))
   289  	for i, comp := range af.ParsedComponents {
   290  		cm, err := af.GenerateComponentManifest(comp, nil)
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  		err = af.SetOAMContract(cm)
   295  		if err != nil {
   296  			return nil, err
   297  		}
   298  		compManifests[i] = cm
   299  		af.Artifacts[i] = cm
   300  	}
   301  	return compManifests, nil
   302  }
   303  
   304  // GenerateComponentManifest generate only one ComponentManifest
   305  func (af *Appfile) GenerateComponentManifest(comp *Component, mutate func(*velaprocess.ContextData)) (*types.ComponentManifest, error) {
   306  	if af.Namespace == "" {
   307  		af.Namespace = corev1.NamespaceDefault
   308  	}
   309  	ctxData := GenerateContextDataFromAppFile(af, comp.Name)
   310  	if mutate != nil {
   311  		mutate(&ctxData)
   312  	}
   313  	// generate context here to avoid nil pointer panic
   314  	comp.Ctx = NewBasicContext(ctxData, comp.Params)
   315  	switch comp.CapabilityCategory {
   316  	case types.TerraformCategory:
   317  		return generateComponentFromTerraformModule(comp, af.Name, af.Namespace)
   318  	default:
   319  		return generateComponentFromCUEModule(comp, ctxData)
   320  	}
   321  }
   322  
   323  // SetOAMContract will set OAM labels and annotations for resources as contract
   324  func (af *Appfile) SetOAMContract(comp *types.ComponentManifest) error {
   325  
   326  	compName := comp.Name
   327  	commonLabels := af.generateAndFilterCommonLabels(compName)
   328  	af.assembleWorkload(comp.ComponentOutput, compName, commonLabels)
   329  
   330  	workloadRef := corev1.ObjectReference{
   331  		APIVersion: comp.ComponentOutput.GetAPIVersion(),
   332  		Kind:       comp.ComponentOutput.GetKind(),
   333  		Name:       comp.ComponentOutput.GetName(),
   334  	}
   335  	for _, trait := range comp.ComponentOutputsAndTraits {
   336  		af.assembleTrait(trait, comp.Name, commonLabels)
   337  		if err := af.setWorkloadRefToTrait(workloadRef, trait); err != nil && !IsNotFoundInAppFile(err) {
   338  			return errors.WithMessagef(err, "cannot set workload reference to trait %q", trait.GetName())
   339  		}
   340  	}
   341  	return nil
   342  }
   343  
   344  // workload and trait in the same component both have these labels, except componentRevision which should be evaluated with input/output
   345  func (af *Appfile) generateAndFilterCommonLabels(compName string) map[string]string {
   346  	filter := func(labels map[string]string, notAllowedKey []string) {
   347  		for _, l := range notAllowedKey {
   348  			delete(labels, strings.TrimSpace(l))
   349  		}
   350  	}
   351  	Labels := map[string]string{
   352  		oam.LabelAppName:      af.Name,
   353  		oam.LabelAppNamespace: af.Namespace,
   354  		oam.LabelAppRevision:  af.AppRevisionName,
   355  		oam.LabelAppComponent: compName,
   356  	}
   357  	// merge application's all labels
   358  	finalLabels := util.MergeMapOverrideWithDst(af.AppLabels, Labels)
   359  	filterLabels, ok := af.AppAnnotations[oam.AnnotationFilterLabelKeys]
   360  	if ok {
   361  		filter(finalLabels, strings.Split(filterLabels, ","))
   362  	}
   363  	return finalLabels
   364  }
   365  
   366  // workload and trait both have these annotations
   367  func (af *Appfile) filterAndSetAnnotations(obj *unstructured.Unstructured) {
   368  	var allFilterAnnotation []string
   369  	allFilterAnnotation = append(allFilterAnnotation, types.DefaultFilterAnnots...)
   370  
   371  	passedFilterAnnotation, ok := af.AppAnnotations[oam.AnnotationFilterAnnotationKeys]
   372  	if ok {
   373  		allFilterAnnotation = append(allFilterAnnotation, strings.Split(passedFilterAnnotation, ",")...)
   374  	}
   375  
   376  	// pass application's all annotations
   377  	util.AddAnnotations(obj, af.AppAnnotations)
   378  	// remove useless annotations for workload/trait
   379  	util.RemoveAnnotations(obj, allFilterAnnotation)
   380  }
   381  
   382  func (af *Appfile) setNamespace(obj *unstructured.Unstructured) {
   383  
   384  	// we should not set namespace for namespace resources
   385  	gvk := obj.GetObjectKind().GroupVersionKind()
   386  	if gvk == corev1.SchemeGroupVersion.WithKind(reflect.TypeOf(corev1.Namespace{}).Name()) {
   387  		return
   388  	}
   389  
   390  	// only set app's namespace when namespace is unspecified
   391  	// it's by design to set arbitrary namespace in render phase
   392  	if len(obj.GetNamespace()) == 0 {
   393  		obj.SetNamespace(af.Namespace)
   394  	}
   395  }
   396  
   397  func (af *Appfile) assembleWorkload(comp *unstructured.Unstructured, compName string, labels map[string]string) {
   398  	// use component name as workload name if workload name is not specified
   399  	// don't override the name set in render phase if exist
   400  	if len(comp.GetName()) == 0 {
   401  		comp.SetName(compName)
   402  	}
   403  	af.setNamespace(comp)
   404  	af.setWorkloadLabels(comp, labels)
   405  	af.filterAndSetAnnotations(comp)
   406  }
   407  
   408  /*
   409  	NOTE a workload has these possible labels
   410  	  app.oam.dev/app-revision-hash: ce053923e2fb403f
   411  	  app.oam.dev/appRevision: myapp-v2
   412  	  app.oam.dev/component: mycomp
   413  	  app.oam.dev/name: myapp
   414  	  app.oam.dev/resourceType: WORKLOAD
   415  	  app.oam.dev/revision: mycomp-v2
   416  	  workload.oam.dev/type: kube-worker
   417  
   418  // Component Revision name was not added here (app.oam.dev/revision: mycomp-v2)
   419  */
   420  func (af *Appfile) setWorkloadLabels(comp *unstructured.Unstructured, commonLabels map[string]string) {
   421  	// add more workload-specific labels here
   422  	util.AddLabels(comp, map[string]string{oam.LabelOAMResourceType: oam.ResourceTypeWorkload})
   423  	util.AddLabels(comp, commonLabels)
   424  }
   425  
   426  func (af *Appfile) assembleTrait(trait *unstructured.Unstructured, compName string, labels map[string]string) {
   427  	if len(trait.GetName()) == 0 {
   428  		traitType := trait.GetLabels()[oam.TraitTypeLabel]
   429  		cpTrait := trait.DeepCopy()
   430  		// remove labels that should not be calculated into hash
   431  		util.RemoveLabels(cpTrait, []string{oam.LabelAppRevision})
   432  		traitName := util.GenTraitName(compName, cpTrait, traitType)
   433  		trait.SetName(traitName)
   434  	}
   435  	af.setTraitLabels(trait, labels)
   436  	af.filterAndSetAnnotations(trait)
   437  	af.setNamespace(trait)
   438  }
   439  
   440  /*
   441  	NOTE a trait has these possible labels
   442  	  app.oam.dev/app-revision-hash: ce053923e2fb403f
   443  	  app.oam.dev/appRevision: myapp-v2
   444  	  app.oam.dev/component: mycomp
   445  	  app.oam.dev/name: myapp
   446  	  app.oam.dev/resourceType: TRAIT
   447  	  trait.oam.dev/resource: service
   448  	  trait.oam.dev/type: ingress // already added in render phase
   449  
   450  // Component Revision name was not added here (app.oam.dev/revision: mycomp-v2)
   451  */
   452  func (af *Appfile) setTraitLabels(trait *unstructured.Unstructured, commonLabels map[string]string) {
   453  	// add more trait-specific labels here
   454  	util.AddLabels(trait, map[string]string{oam.LabelOAMResourceType: oam.ResourceTypeTrait})
   455  	util.AddLabels(trait, commonLabels)
   456  }
   457  
   458  func (af *Appfile) setWorkloadRefToTrait(wlRef corev1.ObjectReference, trait *unstructured.Unstructured) error {
   459  	traitType := trait.GetLabels()[oam.TraitTypeLabel]
   460  	if traitType == definition.AuxiliaryWorkload {
   461  		return nil
   462  	}
   463  	if strings.Contains(traitType, "-") {
   464  		splitName := traitType[0:strings.LastIndex(traitType, "-")]
   465  		_, ok := af.RelatedTraitDefinitions[splitName]
   466  		if ok {
   467  			traitType = splitName
   468  		}
   469  	}
   470  	traitDef, ok := af.RelatedTraitDefinitions[traitType]
   471  	if !ok {
   472  		return errors.Errorf("TraitDefinition %s not found in appfile", traitType)
   473  	}
   474  	workloadRefPath := traitDef.Spec.WorkloadRefPath
   475  	// only add workload reference to the trait if it asks for it
   476  	if len(workloadRefPath) != 0 {
   477  		tmpWLRef := corev1.ObjectReference{
   478  			APIVersion: wlRef.APIVersion,
   479  			Kind:       wlRef.Kind,
   480  			Name:       wlRef.Name,
   481  		}
   482  		if err := fieldpath.Pave(trait.UnstructuredContent()).SetValue(workloadRefPath, tmpWLRef); err != nil {
   483  			return err
   484  		}
   485  	}
   486  	return nil
   487  }
   488  
   489  // IsNotFoundInAppFile check if the target error is `not found in appfile`
   490  func IsNotFoundInAppFile(err error) bool {
   491  	return err != nil && strings.Contains(err.Error(), "not found in appfile")
   492  }
   493  
   494  // PrepareProcessContext prepares a DSL process Context
   495  func PrepareProcessContext(comp *Component, ctxData velaprocess.ContextData) (process.Context, error) {
   496  	if comp.Ctx == nil {
   497  		comp.Ctx = NewBasicContext(ctxData, comp.Params)
   498  	}
   499  	if err := comp.EvalContext(comp.Ctx); err != nil {
   500  		return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", ctxData.AppName, ctxData.Namespace)
   501  	}
   502  	return comp.Ctx, nil
   503  }
   504  
   505  // NewBasicContext prepares a basic DSL process Context
   506  func NewBasicContext(contextData velaprocess.ContextData, params map[string]interface{}) process.Context {
   507  	pCtx := velaprocess.NewContext(contextData)
   508  	if params != nil {
   509  		pCtx.SetParameters(params)
   510  	}
   511  	return pCtx
   512  }
   513  
   514  func generateComponentFromCUEModule(comp *Component, ctxData velaprocess.ContextData) (*types.ComponentManifest, error) {
   515  	pCtx, err := PrepareProcessContext(comp, ctxData)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  	return baseGenerateComponent(pCtx, comp, ctxData.AppName, ctxData.Namespace)
   520  }
   521  
   522  func generateComponentFromTerraformModule(comp *Component, appName, ns string) (*types.ComponentManifest, error) {
   523  	return baseGenerateComponent(comp.Ctx, comp, appName, ns)
   524  }
   525  
   526  func baseGenerateComponent(pCtx process.Context, comp *Component, appName, ns string) (*types.ComponentManifest, error) {
   527  	var err error
   528  	pCtx.PushData(velaprocess.ContextComponentType, comp.Type)
   529  	for _, tr := range comp.Traits {
   530  		if err := tr.EvalContext(pCtx); err != nil {
   531  			return nil, errors.Wrapf(err, "evaluate template trait=%s app=%s", tr.Name, comp.Name)
   532  		}
   533  	}
   534  	if patcher := comp.Patch; patcher != nil {
   535  		workload, auxiliaries := pCtx.Output()
   536  		if p, err := patcher.LookupValue("workload"); err == nil {
   537  			if err := workload.Unify(p.CueValue()); err != nil {
   538  				return nil, errors.WithMessage(err, "patch workload")
   539  			}
   540  		}
   541  		for _, aux := range auxiliaries {
   542  			if p, err := patcher.LookupByScript(fmt.Sprintf("traits[\"%s\"]", aux.Name)); err == nil && p.CueValue().Err() == nil {
   543  				if err := aux.Ins.Unify(p.CueValue()); err != nil {
   544  					return nil, errors.WithMessagef(err, "patch outputs.%s", aux.Name)
   545  				}
   546  			}
   547  		}
   548  	}
   549  	compManifest, err := evalWorkloadWithContext(pCtx, comp, ns, appName)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	compManifest.Name = comp.Name
   554  	compManifest.Namespace = ns
   555  	return compManifest, nil
   556  }
   557  
   558  // makeWorkloadWithContext evaluate the workload's template to unstructured resource.
   559  func makeWorkloadWithContext(pCtx process.Context, comp *Component, ns, appName string) (*unstructured.Unstructured, error) {
   560  	var (
   561  		workload *unstructured.Unstructured
   562  		err      error
   563  	)
   564  	base, _ := pCtx.Output()
   565  	switch comp.CapabilityCategory {
   566  	case types.TerraformCategory:
   567  		workload, err = generateTerraformConfigurationWorkload(comp, ns)
   568  		if err != nil {
   569  			return nil, errors.Wrapf(err, "failed to generate Terraform Configuration workload for workload %s", comp.Name)
   570  		}
   571  	default:
   572  		workload, err = base.Unstructured()
   573  		if err != nil {
   574  			return nil, errors.Wrapf(err, "evaluate base template component=%s app=%s", comp.Name, appName)
   575  		}
   576  	}
   577  	commonLabels := definition.GetCommonLabels(definition.GetBaseContextLabels(pCtx))
   578  	util.AddLabels(workload, util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.WorkloadTypeLabel: comp.Type}))
   579  	return workload, nil
   580  }
   581  
   582  // evalWorkloadWithContext evaluate the workload's template to generate component manifest
   583  func evalWorkloadWithContext(pCtx process.Context, comp *Component, ns, appName string) (*types.ComponentManifest, error) {
   584  	compManifest := &types.ComponentManifest{}
   585  	workload, err := makeWorkloadWithContext(pCtx, comp, ns, appName)
   586  	if err != nil {
   587  		return nil, err
   588  	}
   589  	compManifest.ComponentOutput = workload
   590  
   591  	_, assists := pCtx.Output()
   592  	compManifest.ComponentOutputsAndTraits = make([]*unstructured.Unstructured, len(assists))
   593  	commonLabels := definition.GetCommonLabels(definition.GetBaseContextLabels(pCtx))
   594  	for i, assist := range assists {
   595  		tr, err := assist.Ins.Unstructured()
   596  		if err != nil {
   597  			return nil, errors.Wrapf(err, "evaluate trait=%s template for component=%s app=%s", assist.Name, comp.Name, appName)
   598  		}
   599  		labels := util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.TraitTypeLabel: assist.Type})
   600  		if assist.Name != "" {
   601  			labels[oam.TraitResource] = assist.Name
   602  		}
   603  		util.AddLabels(tr, labels)
   604  		compManifest.ComponentOutputsAndTraits[i] = tr
   605  	}
   606  	return compManifest, nil
   607  }
   608  
   609  func generateTerraformConfigurationWorkload(comp *Component, ns string) (*unstructured.Unstructured, error) {
   610  	if comp.FullTemplate == nil || comp.FullTemplate.Terraform == nil || comp.FullTemplate.Terraform.Configuration == "" {
   611  		return nil, errors.New(errTerraformConfigurationIsNotSet)
   612  	}
   613  	params, err := json.Marshal(comp.Params)
   614  	if err != nil {
   615  		return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
   616  	}
   617  
   618  	if comp.FullTemplate.ComponentDefinition == nil || comp.FullTemplate.ComponentDefinition.Spec.Schematic == nil ||
   619  		comp.FullTemplate.ComponentDefinition.Spec.Schematic.Terraform == nil {
   620  		return nil, errors.New(errTerraformComponentDefinition)
   621  	}
   622  
   623  	configuration := terraformapi.Configuration{
   624  		TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta2", Kind: "Configuration"},
   625  		ObjectMeta: metav1.ObjectMeta{
   626  			Name:        comp.Name,
   627  			Namespace:   ns,
   628  			Annotations: comp.FullTemplate.ComponentDefinition.Annotations,
   629  		},
   630  	}
   631  	// 1. parse the spec of configuration
   632  	var spec terraformapi.ConfigurationSpec
   633  	if err := json.Unmarshal(params, &spec); err != nil {
   634  		return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
   635  	}
   636  	configuration.Spec = spec
   637  
   638  	if configuration.Spec.WriteConnectionSecretToReference == nil {
   639  		configuration.Spec.WriteConnectionSecretToReference = comp.FullTemplate.ComponentDefinition.Spec.Schematic.Terraform.WriteConnectionSecretToReference
   640  	}
   641  	if configuration.Spec.WriteConnectionSecretToReference != nil && configuration.Spec.WriteConnectionSecretToReference.Namespace == "" {
   642  		configuration.Spec.WriteConnectionSecretToReference.Namespace = ns
   643  	}
   644  
   645  	if configuration.Spec.ProviderReference == nil {
   646  		configuration.Spec.ProviderReference = comp.FullTemplate.ComponentDefinition.Spec.Schematic.Terraform.ProviderReference
   647  	}
   648  
   649  	if configuration.Spec.GitCredentialsSecretReference == nil {
   650  		configuration.Spec.GitCredentialsSecretReference = comp.FullTemplate.ComponentDefinition.Spec.Schematic.Terraform.GitCredentialsSecretReference
   651  	}
   652  
   653  	switch comp.FullTemplate.Terraform.Type {
   654  	case "hcl":
   655  		configuration.Spec.HCL = comp.FullTemplate.Terraform.Configuration
   656  	case "remote":
   657  		configuration.Spec.Remote = comp.FullTemplate.Terraform.Configuration
   658  		configuration.Spec.Path = comp.FullTemplate.Terraform.Path
   659  	}
   660  
   661  	// 2. parse variable
   662  	variableRaw := &runtime.RawExtension{}
   663  	if err := json.Unmarshal(params, &variableRaw); err != nil {
   664  		return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
   665  	}
   666  
   667  	variableMap, err := util.RawExtension2Map(variableRaw)
   668  	if err != nil {
   669  		return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
   670  	}
   671  	delete(variableMap, WriteConnectionSecretToRefKey)
   672  	delete(variableMap, RegionKey)
   673  	delete(variableMap, ProviderRefKey)
   674  	delete(variableMap, ForceDeleteKey)
   675  	delete(variableMap, GitCredentialsSecretReferenceKey)
   676  
   677  	data, err := json.Marshal(variableMap)
   678  	if err != nil {
   679  		return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
   680  	}
   681  
   682  	configuration.Spec.Variable = &runtime.RawExtension{Raw: data}
   683  	raw := util.Object2RawExtension(&configuration)
   684  	return util.RawExtension2Unstructured(raw)
   685  }
   686  
   687  // a helper map whose key is parameter name
   688  type paramValueSettings map[string]paramValueSetting
   689  type paramValueSetting struct {
   690  	Value      interface{}
   691  	ValueType  common.ParameterValueType
   692  	FieldPaths []string
   693  }
   694  
   695  func setParameterValuesToKubeObj(obj *unstructured.Unstructured, values paramValueSettings) error {
   696  	paved := fieldpath.Pave(obj.Object)
   697  	for paramName, v := range values {
   698  		for _, f := range v.FieldPaths {
   699  			switch v.ValueType {
   700  			case common.StringType:
   701  				vString, ok := v.Value.(string)
   702  				if !ok {
   703  					return errors.Errorf(errInvalidValueType, v.ValueType)
   704  				}
   705  				if err := paved.SetString(f, vString); err != nil {
   706  					return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
   707  				}
   708  			case common.NumberType:
   709  				switch v.Value.(type) {
   710  				case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
   711  					if err := paved.SetValue(f, v.Value); err != nil {
   712  						return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
   713  					}
   714  				default:
   715  					return errors.Errorf(errInvalidValueType, v.ValueType)
   716  				}
   717  			case common.BooleanType:
   718  				vBoolean, ok := v.Value.(bool)
   719  				if !ok {
   720  					return errors.Errorf(errInvalidValueType, v.ValueType)
   721  				}
   722  				if err := paved.SetValue(f, vBoolean); err != nil {
   723  					return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
   724  				}
   725  			}
   726  		}
   727  	}
   728  	return nil
   729  }
   730  
   731  // GenerateContextDataFromAppFile generates process context data from app file
   732  func GenerateContextDataFromAppFile(appfile *Appfile, wlName string) velaprocess.ContextData {
   733  	data := velaprocess.ContextData{
   734  		Namespace:       appfile.Namespace,
   735  		AppName:         appfile.Name,
   736  		CompName:        wlName,
   737  		AppRevisionName: appfile.AppRevisionName,
   738  		Components:      appfile.Components,
   739  	}
   740  	if appfile.AppAnnotations != nil {
   741  		data.WorkflowName = appfile.AppAnnotations[oam.AnnotationWorkflowName]
   742  		data.PublishVersion = appfile.AppAnnotations[oam.AnnotationPublishVersion]
   743  		data.AppAnnotations = appfile.AppAnnotations
   744  	}
   745  	if appfile.AppLabels != nil {
   746  		data.AppLabels = appfile.AppLabels
   747  	}
   748  	return data
   749  }
   750  
   751  // WorkflowClient cache retrieved workflow if ApplicationRevision not exists in appfile
   752  // else use the workflow in ApplicationRevision
   753  func (af *Appfile) WorkflowClient(cli client.Client) client.Client {
   754  	return velaclient.DelegatingHandlerClient{
   755  		Client: cli,
   756  		Getter: func(ctx context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
   757  			if wf, ok := obj.(*workflowv1alpha1.Workflow); ok {
   758  				if af.AppRevision != nil {
   759  					if af.ExternalWorkflow != nil && af.ExternalWorkflow.Name == key.Name && af.ExternalWorkflow.Namespace == key.Namespace {
   760  						af.ExternalWorkflow.DeepCopyInto(wf)
   761  						return nil
   762  					}
   763  					return kerrors.NewNotFound(v1alpha1.SchemeGroupVersion.WithResource("workflow").GroupResource(), key.Name)
   764  				}
   765  				if err := cli.Get(ctx, key, obj); err != nil {
   766  					return err
   767  				}
   768  				af.ExternalWorkflow = obj.(*workflowv1alpha1.Workflow)
   769  				return nil
   770  			}
   771  			return cli.Get(ctx, key, obj)
   772  		},
   773  	}
   774  }
   775  
   776  // PolicyClient cache retrieved policy if ApplicationRevision not exists in appfile
   777  // else use the policy in ApplicationRevision
   778  func (af *Appfile) PolicyClient(cli client.Client) client.Client {
   779  	return velaclient.DelegatingHandlerClient{
   780  		Client: cli,
   781  		Getter: func(ctx context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
   782  			if po, ok := obj.(*v1alpha1.Policy); ok {
   783  				if af.AppRevision != nil {
   784  					if p, found := af.ExternalPolicies[key.String()]; found {
   785  						p.DeepCopyInto(po)
   786  						return nil
   787  					}
   788  					return kerrors.NewNotFound(v1alpha1.SchemeGroupVersion.WithResource("policy").GroupResource(), key.Name)
   789  				}
   790  				if err := cli.Get(ctx, key, obj); err != nil {
   791  					return err
   792  				}
   793  				af.ExternalPolicies[key.String()] = obj.(*v1alpha1.Policy)
   794  				return nil
   795  			}
   796  			return cli.Get(ctx, key, obj)
   797  		},
   798  	}
   799  }
   800  
   801  // LoadDynamicComponent for ref-objects typed components, this function will load referred objects from stored revisions
   802  func (af *Appfile) LoadDynamicComponent(ctx context.Context, cli client.Client, comp *common.ApplicationComponent) (*common.ApplicationComponent, error) {
   803  	if comp.Type != v1alpha1.RefObjectsComponentType {
   804  		return comp, nil
   805  	}
   806  	_comp := comp.DeepCopy()
   807  	spec := &v1alpha1.RefObjectsComponentSpec{}
   808  	if err := json.Unmarshal(comp.Properties.Raw, spec); err != nil {
   809  		return nil, errors.Wrapf(err, "invalid ref-objects component properties")
   810  	}
   811  	var uns []*unstructured.Unstructured
   812  	ctx = auth.ContextWithUserInfo(ctx, af.app)
   813  	for _, selector := range spec.Objects {
   814  		objs, err := component.SelectRefObjectsForDispatch(ctx, component.ReferredObjectsDelegatingClient(cli, af.ReferredObjects), af.Namespace, comp.Name, selector)
   815  		if err != nil {
   816  			return nil, errors.Wrapf(err, "failed to select objects from referred objects in revision storage")
   817  		}
   818  		uns = component.AppendUnstructuredObjects(uns, objs...)
   819  	}
   820  	// nolint
   821  	for _, url := range spec.URLs {
   822  		objs := slices.Filter(af.ReferredObjects, func(obj *unstructured.Unstructured) bool {
   823  			return obj.GetAnnotations() != nil && obj.GetAnnotations()[oam.AnnotationResourceURL] == url
   824  		})
   825  		uns = component.AppendUnstructuredObjects(uns, objs...)
   826  	}
   827  	refObjs, err := component.ConvertUnstructuredsToReferredObjects(uns)
   828  	if err != nil {
   829  		return nil, errors.Wrapf(err, "failed to marshal referred object")
   830  	}
   831  	bs, err := json.Marshal(&common.ReferredObjectList{Objects: refObjs})
   832  	if err != nil {
   833  		return nil, errors.Wrapf(err, "failed to marshal loaded ref-objects")
   834  	}
   835  	_comp.Properties = &runtime.RawExtension{Raw: bs}
   836  	return _comp, nil
   837  }