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 }