github.com/oam-dev/kubevela@v1.9.11/pkg/appfile/template.go (about)

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