Files
kubevela/pkg/cue/definition/template.go
Amit Singh 0b85d55e68
Some checks failed
Webhook Upgrade Validation / webhook-upgrade-check (push) Failing after 1m46s
Feat: post dispatch output context (#7008)
* exploring context data passing

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* adds output status fetch logic

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix: standardize  import in dispatcher.

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* feat: Allow  traits to access workload output status in CUE context

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* feat: Implement PostDispatch traits that apply after component health is confirmed.

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* feat: Refactor  trait handling and status propagation in application dispatch.

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix: run make reviewable

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* feat: Implement and document PostDispatch traits, applying them after component health is confirmed and guarded by a feature flag, along with new example applications.

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* feat: Add comments

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Fix: Restore the status field in ctx.

Signed-off-by: Vaibhav Agrawal <vaibhav.agrawal0096@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Fix: Error for evaluating the status of the trait

Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* refactor: removes minor unnecessary changes

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* refactor: minor linter changes

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* test: Add comprehensive tests for PostDispatch traits and their status handling

Signed-off-by: Reetika Malhotra <malhotra.reetika25@gmail.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Fix: Increase multi-cluster test time

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Chore: Add focus and print the application status

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Chore: print deployment status in the multicluster test

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Chore: add labels for the deployment

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* debugging test failure

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* debugging test failure by updating multi cluster ctx

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* undoes multi cluster ctx change

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Feat: enable MultiStageComponentApply feature by default

Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Feat: implement post-dispatch traits application in workflow states

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Chore: remove unnecessary blank lines in application_controller.go

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Feat: enhance output readiness handling in health checks

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Feat: add logic to determine need for post-dispatch outputs in workload processing

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* Feat: enhance output extraction and dependency checking for post-dispatch traits

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix code to exclude validation of post dispatch trait in webhook

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix code to exclude validation of post dispatch trait in webhook

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* commit for running the test again

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* commit for running the test again

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* commit for running the test again

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* triggering checks

Signed-off-by: Amit Singh <singhamitch@outlook.com>

* chore: adds explanation comments

Signed-off-by: Amit Singh <singhamitch@outlook.com>

* chore: adds errors to context

Signed-off-by: Amit Singh <singhamitch@outlook.com>

* chore: minor improvements

Signed-off-by: Amit Singh <singhamitch@outlook.com>

* fix: update output handling for pending PostDispatch traits

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix: improve output handling for PostDispatch traits in deploy process

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix: streamline output handling in PostDispatch process

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* chore: commit to re run the pipeline

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* chore: commit to re run the pipeline

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* chore: commit to re run the pipeline

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix: enhance output status handling in PostDispatch context for multi-stage support

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* chore: commit to re run the pipeline

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix: increase timeout for PostDispatch trait verification in tests

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* fix: enhance output status handling in PostDispatch context for multi-stage support

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

* chore: commit to re run the pipeline

Signed-off-by: Vishal Kumar <vishal210893@gmail.com>

---------

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: semmet95 <singhamitch@outlook.com>
Signed-off-by: Vishal Kumar <vishal210893@gmail.com>
Signed-off-by: Chaitanyareddy0702 <chaitanyareddy0702@gmail.com>
Signed-off-by: Vaibhav Agrawal <vaibhav.agrawal0096@gmail.com>
Signed-off-by: Reetika Malhotra <malhotra.reetika25@gmail.com>
Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
Co-authored-by: Chitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
Co-authored-by: Vishal Kumar <vishal210893@gmail.com>
2026-01-14 10:28:13 +00:00

604 lines
19 KiB
Go

/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package definition
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"k8s.io/apiserver/pkg/util/feature"
"github.com/oam-dev/kubevela/pkg/cue/definition/health"
"github.com/oam-dev/kubevela/pkg/features"
"github.com/kubevela/pkg/cue/cuex"
"cuelang.org/go/cue"
cueerrors "cuelang.org/go/cue/errors"
"github.com/kubevela/pkg/multicluster"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubevela/workflow/pkg/cue/model"
"github.com/kubevela/workflow/pkg/cue/model/sets"
"github.com/kubevela/workflow/pkg/cue/model/value"
"github.com/kubevela/workflow/pkg/cue/process"
velaprocess "github.com/oam-dev/kubevela/pkg/cue/process"
"github.com/oam-dev/kubevela/pkg/cue/task"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
const (
// OutputFieldName is the name of the struct contains the CR data
OutputFieldName = velaprocess.OutputFieldName
// OutputsFieldName is the name of the struct contains the map[string]CR data
OutputsFieldName = velaprocess.OutputsFieldName
// PatchFieldName is the name of the struct contains the patch of CR data
PatchFieldName = "patch"
// PatchOutputsFieldName is the name of the struct contains the patch of outputs CR data
PatchOutputsFieldName = "patchOutputs"
// ErrsFieldName check if errors contained in the cue
ErrsFieldName = "errs"
// TemplateContextPrefix is the base prefix for storing templates in context
TemplateContextPrefix = "template-context-"
)
// GetWorkloadTemplateKey returns the context key for storing workload templates
func GetWorkloadTemplateKey(name string) string {
return TemplateContextPrefix + "workload-" + name
}
// GetTraitTemplateKey returns the context key for storing trait templates
func GetTraitTemplateKey(name string) string {
return TemplateContextPrefix + "trait-" + name
}
const (
// AuxiliaryWorkload defines the extra workload obj from a workloadDefinition,
// e.g. a workload composed by deployment and service, the service will be marked as AuxiliaryWorkload
AuxiliaryWorkload = "AuxiliaryWorkload"
)
// AbstractEngine defines Definition's Render interface
type AbstractEngine interface {
Complete(ctx process.Context, abstractTemplate string, params interface{}) error
Status(templateContext map[string]interface{}, request *health.StatusRequest) (*health.StatusResult, error)
GetTemplateContext(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor) (map[string]interface{}, error)
}
type def struct {
name string
}
type workloadDef struct {
def
}
// NewWorkloadAbstractEngine create Workload Definition AbstractEngine
func NewWorkloadAbstractEngine(name string) AbstractEngine {
return &workloadDef{
def: def{
name: name,
},
}
}
// Complete do workload definition's rendering
func (wd *workloadDef) Complete(ctx process.Context, abstractTemplate string, params interface{}) error {
var paramFile = velaprocess.ParameterFieldName + ": {}"
if params != nil {
bt, err := json.Marshal(params)
if err != nil {
return errors.WithMessagef(err, "marshal parameter of workload %s", wd.name)
}
if string(bt) != "null" {
paramFile = fmt.Sprintf("%s: %s", velaprocess.ParameterFieldName, string(bt))
}
}
c, err := ctx.BaseContextFile()
if err != nil {
return err
}
val, err := cuex.DefaultCompiler.Get().CompileString(ctx.GetCtx(), strings.Join([]string{
renderTemplate(abstractTemplate), paramFile, c,
}, "\n"))
if err != nil {
return errors.WithMessagef(err, "failed to compile workload %s after merge parameter and context", wd.name)
}
if err := val.Validate(); err != nil {
if fmtErr := FormatCUEError(err, "validation failed for", "workload", wd.name, &val); fmtErr != nil {
return fmtErr
}
return fmt.Errorf("validation failed for workload %s: %w", wd.name, err)
}
output := val.LookupPath(value.FieldPath(OutputFieldName))
base, err := model.NewBase(output)
if err != nil {
return errors.WithMessagef(err, "invalid output of workload %s", wd.name)
}
if err := ctx.SetBase(base); err != nil {
return err
}
// Store template for error context (use workload-specific key to avoid pollution)
ctx.PushData(GetWorkloadTemplateKey(wd.name), val)
// we will support outputs for workload composition, and it will become trait in AppConfig.
outputs := val.LookupPath(value.FieldPath(OutputsFieldName))
if !outputs.Exists() {
return nil
}
iter, err := outputs.Fields(cue.Definitions(true), cue.Hidden(true), cue.All())
if err != nil {
return errors.WithMessagef(err, "invalid outputs of workload %s", wd.name)
}
for iter.Next() {
if iter.Selector().IsDefinition() || iter.Selector().PkgPath() != "" || iter.IsOptional() {
continue
}
other, err := model.NewOther(iter.Value())
name := util.GetIteratorLabel(*iter)
if err != nil {
return errors.WithMessagef(err, "invalid outputs(%s) of workload %s", name, wd.name)
}
if err := ctx.AppendAuxiliaries(process.Auxiliary{Ins: other, Type: AuxiliaryWorkload, Name: name}); err != nil {
return err
}
}
return nil
}
func withCluster(ctx context.Context, o client.Object) context.Context {
if cluster := oam.GetCluster(o); cluster != "" {
return multicluster.WithCluster(ctx, cluster)
}
return ctx
}
func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
baseLabels := GetBaseContextLabels(ctx)
var root = initRoot(baseLabels)
var commonLabels = GetCommonLabels(baseLabels)
base, assists := ctx.Output()
componentWorkload, err := base.Unstructured()
if err != nil {
return nil, err
}
// workload main resource will have a unique label("app.oam.dev/resourceType"="WORKLOAD") in per component/app level
_ctx := withCluster(ctx.GetCtx(), componentWorkload)
object, err := getResourceFromObj(_ctx, ctx, componentWorkload, cli, accessor.For(componentWorkload), util.MergeMapOverrideWithDst(map[string]string{
oam.LabelOAMResourceType: oam.ResourceTypeWorkload,
}, commonLabels), "")
if err != nil {
return nil, err
}
root[OutputFieldName] = object
outputs := make(map[string]interface{})
for _, assist := range assists {
if assist.Type != AuxiliaryWorkload {
continue
}
if assist.Name == "" {
return nil, errors.New("the auxiliary of workload must have a name with format 'outputs.<my-name>'")
}
traitRef, err := assist.Ins.Unstructured()
if err != nil {
return nil, err
}
// AuxiliaryWorkload will have a unique label("trait.oam.dev/resource"="name of outputs") in per component/app level
_ctx := withCluster(ctx.GetCtx(), traitRef)
object, err := getResourceFromObj(_ctx, ctx, traitRef, cli, accessor.For(traitRef), util.MergeMapOverrideWithDst(map[string]string{
oam.TraitTypeLabel: AuxiliaryWorkload,
}, commonLabels), assist.Name)
if err != nil {
return nil, err
}
outputs[assist.Name] = object
}
if len(outputs) > 0 {
root[OutputsFieldName] = outputs
}
return root, nil
}
// Status get workload status by customStatusTemplate
func (wd *workloadDef) Status(templateContext map[string]interface{}, request *health.StatusRequest) (*health.StatusResult, error) {
return health.GetStatus(templateContext, request)
}
func (wd *workloadDef) GetTemplateContext(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
return wd.getTemplateContext(ctx, cli, accessor)
}
type traitDef struct {
def
}
// NewTraitAbstractEngine create Trait Definition AbstractEngine
func NewTraitAbstractEngine(name string) AbstractEngine {
return &traitDef{
def: def{
name: name,
},
}
}
// Complete do trait definition's rendering
// nolint:gocyclo
func (td *traitDef) Complete(ctx process.Context, abstractTemplate string, params interface{}) error {
buff := abstractTemplate + "\n"
if params != nil {
bt, err := json.Marshal(params)
if err != nil {
return errors.WithMessagef(err, "marshal parameter of trait %s", td.name)
}
if string(bt) != "null" {
buff += fmt.Sprintf("%s: %s\n", velaprocess.ParameterFieldName, string(bt))
}
}
multiStageEnabled := feature.DefaultMutableFeatureGate.Enabled(features.MultiStageComponentApply)
var statusBytes []byte
if multiStageEnabled {
statusBytes = outputStatusBytes(ctx)
}
c, err := ctx.BaseContextFile()
if err != nil {
return err
}
// When multi-stage is enabled, merge the existing output.status from ctx into the
// base context so downstream CUE can reference it deterministically.
if multiStageEnabled {
c = injectOutputStatusIntoBaseContext(ctx, c, statusBytes)
}
buff += c
val, err := cuex.DefaultCompiler.Get().CompileString(ctx.GetCtx(), buff)
if err != nil {
return errors.WithMessagef(err, "failed to compile trait %s after merge parameter and context", td.name)
}
if err := val.Validate(); err != nil {
if fmtErr := FormatCUEError(err, "validation failed for", "trait", td.name, &val); fmtErr != nil {
return fmtErr
}
return fmt.Errorf("validation failed for trait %s: %w", td.name, err)
}
processing := val.LookupPath(value.FieldPath("processing"))
if processing.Exists() {
if val, err = task.Process(val); err != nil {
return errors.WithMessagef(err, "invalid process of trait %s", td.name)
}
}
outputs := val.LookupPath(value.FieldPath(OutputsFieldName))
if outputs.Exists() {
iter, err := outputs.Fields(cue.Definitions(true), cue.Hidden(true), cue.All())
if err != nil {
return errors.WithMessagef(err, "invalid outputs of trait %s", td.name)
}
for iter.Next() {
if iter.Selector().IsDefinition() || iter.Selector().PkgPath() != "" || iter.IsOptional() {
continue
}
other, err := model.NewOther(iter.Value())
name := util.GetIteratorLabel(*iter)
if err != nil {
return errors.WithMessagef(err, "invalid outputs(resource=%s) of trait %s", name, td.name)
}
if err := ctx.AppendAuxiliaries(process.Auxiliary{Ins: other, Type: td.name, Name: name}); err != nil {
return err
}
}
}
patcher := val.LookupPath(value.FieldPath(PatchFieldName))
base, auxiliaries := ctx.Output()
if patcher.Exists() {
if base == nil {
return fmt.Errorf("patch trait %s into an invalid workload", td.name)
}
if err := base.Unify(patcher, sets.CreateUnifyOptionsForPatcher(patcher)...); err != nil {
return errors.WithMessagef(err, "invalid patch trait %s into workload", td.name)
}
}
outputsPatcher := val.LookupPath(value.FieldPath(PatchOutputsFieldName))
if outputsPatcher.Exists() {
for _, auxiliary := range auxiliaries {
target := outputsPatcher.LookupPath(value.FieldPath(auxiliary.Name))
if !target.Exists() {
continue
}
if err = auxiliary.Ins.Unify(target); err != nil {
return errors.WithMessagef(err, "trait=%s, to=%s, invalid patch trait into auxiliary workload", td.name, auxiliary.Name)
}
}
}
errs := val.LookupPath(value.FieldPath(ErrsFieldName))
if errs.Exists() {
if err := parseErrors(errs); err != nil {
return err
}
}
return nil
}
func outputStatusBytes(ctx process.Context) []byte {
var statusBytes []byte
var outputMap map[string]interface{}
if output := ctx.GetData(OutputFieldName); output != nil {
if m, ok := output.(map[string]interface{}); ok {
outputMap = m
} else if ptr, ok := output.(*interface{}); ok && ptr != nil {
if m, ok := (*ptr).(map[string]interface{}); ok {
outputMap = m
}
}
if outputMap != nil {
if status, ok := outputMap["status"]; ok {
if b, err := json.Marshal(status); err == nil {
statusBytes = b
}
}
}
}
return statusBytes
}
func injectOutputStatusIntoBaseContext(ctx process.Context, c string, statusBytes []byte) string {
if len(statusBytes) > 0 {
// If output is an empty object, replace it with only the status field without trailing comma.
emptyOutputMarker := "\"output\":{}"
if strings.Contains(c, emptyOutputMarker) {
replacement := fmt.Sprintf("\"output\":{\"status\":%s}", string(statusBytes))
c = strings.Replace(c, emptyOutputMarker, replacement, 1)
} else {
// Otherwise, insert status as the first field and keep the comma to separate from existing fields.
replacement := fmt.Sprintf("\"output\":{\"status\":%s,", string(statusBytes))
c = strings.Replace(c, "\"output\":{", replacement, 1)
}
// Restore the status field to the current output in ctx.data
var status interface{}
if err := json.Unmarshal(statusBytes, &status); err == nil {
if currentOutput := ctx.GetData(OutputFieldName); currentOutput != nil {
if currentMap, ok := currentOutput.(map[string]interface{}); ok {
currentMap["status"] = status
ctx.PushData(OutputFieldName, currentMap)
}
}
}
}
return c
}
func parseErrors(errs cue.Value) error {
if it, e := errs.List(); e == nil {
for it.Next() {
if s, err := it.Value().String(); err == nil && s != "" {
return errors.Errorf("%s", s)
}
}
}
return nil
}
// GetCommonLabels will convert context based labels to OAM standard labels
func GetCommonLabels(contextLabels map[string]string) map[string]string {
var commonLabels = map[string]string{}
for k, v := range contextLabels {
switch k {
case velaprocess.ContextAppName:
commonLabels[oam.LabelAppName] = v
case velaprocess.ContextName:
commonLabels[oam.LabelAppComponent] = v
case velaprocess.ContextAppRevision:
commonLabels[oam.LabelAppRevision] = v
case velaprocess.ContextReplicaKey:
commonLabels[oam.LabelReplicaKey] = v
}
}
return commonLabels
}
// GetBaseContextLabels get base context labels
func GetBaseContextLabels(ctx process.Context) map[string]string {
baseLabels := ctx.BaseContextLabels()
baseLabels[velaprocess.ContextAppName] = ctx.GetData(velaprocess.ContextAppName).(string)
baseLabels[velaprocess.ContextAppRevision] = ctx.GetData(velaprocess.ContextAppRevision).(string)
return baseLabels
}
func initRoot(contextLabels map[string]string) map[string]interface{} {
var root = map[string]interface{}{}
for k, v := range contextLabels {
root[k] = v
}
return root
}
func renderTemplate(templ string) string {
return templ + `
context: _
parameter: _
`
}
func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
baseLabels := GetBaseContextLabels(ctx)
var root = initRoot(baseLabels)
var commonLabels = GetCommonLabels(baseLabels)
_, assists := ctx.Output()
outputs := make(map[string]interface{})
for _, assist := range assists {
if assist.Type != td.name {
continue
}
traitRef, err := assist.Ins.Unstructured()
if err != nil {
return nil, err
}
_ctx := withCluster(ctx.GetCtx(), traitRef)
object, err := getResourceFromObj(_ctx, ctx, traitRef, cli, accessor.For(traitRef), util.MergeMapOverrideWithDst(map[string]string{
oam.TraitTypeLabel: assist.Type,
}, commonLabels), assist.Name)
if err != nil {
return nil, err
}
outputs[assist.Name] = object
}
if len(outputs) > 0 {
root[OutputsFieldName] = outputs
}
return root, nil
}
// Status get trait status by customStatusTemplate
func (td *traitDef) Status(templateContext map[string]interface{}, request *health.StatusRequest) (*health.StatusResult, error) {
return health.GetStatus(templateContext, request)
}
func (td *traitDef) GetTemplateContext(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
return td.getTemplateContext(ctx, cli, accessor)
}
func getResourceFromObj(ctx context.Context, pctx process.Context, obj *unstructured.Unstructured, client client.Reader, namespace string, labels map[string]string, outputsResource string) (map[string]interface{}, error) {
if outputsResource != "" {
labels[oam.TraitResource] = outputsResource
}
if obj.GetName() != "" {
u, err := util.GetObjectGivenGVKAndName(ctx, client, obj.GroupVersionKind(), namespace, obj.GetName())
if err != nil {
return nil, err
}
return u.Object, nil
}
if ctxName := pctx.GetData(model.ContextName).(string); ctxName != "" {
u, err := util.GetObjectGivenGVKAndName(ctx, client, obj.GroupVersionKind(), namespace, ctxName)
if err == nil {
return u.Object, nil
}
}
list, err := util.GetObjectsGivenGVKAndLabels(ctx, client, obj.GroupVersionKind(), namespace, labels)
if err != nil {
return nil, err
}
if len(list.Items) == 1 {
return list.Items[0].Object, nil
}
for _, v := range list.Items {
if v.GetLabels()[oam.TraitResource] == outputsResource {
return v.Object, nil
}
}
return nil, errors.Errorf("no resources found gvk(%v) labels(%v)", obj.GroupVersionKind(), labels)
}
// FormatCUEError formats CUE errors in a user-friendly grouped format
func FormatCUEError(err error, messagePrefix string, entityType, entityName string, val ...*cue.Value) error {
if err == nil {
return nil
}
var allParamErrors = make(map[string]bool)
var allTemplateErrors = make(map[string]bool)
errList := cueerrors.Errors(err)
for _, e := range errList {
errMsg := e.Error()
if strings.HasPrefix(errMsg, "parameter.") {
allParamErrors[errMsg] = true
} else {
allTemplateErrors[errMsg] = true
}
}
// Run concrete validation if val provided to catch missing parameters
if len(val) > 0 && val[0] != nil {
if concreteErr := val[0].Validate(cue.Concrete(true)); concreteErr != nil {
concreteErrList := cueerrors.Errors(concreteErr)
for _, e := range concreteErrList {
errMsg := e.Error()
if strings.HasPrefix(errMsg, "parameter.") {
allParamErrors[errMsg] = true
} else {
allTemplateErrors[errMsg] = true
}
}
}
}
if len(allParamErrors) == 0 && len(allTemplateErrors) == 0 {
return nil
}
var result strings.Builder
result.WriteString(fmt.Sprintf("%s %s %s:", messagePrefix, entityType, entityName))
if len(allParamErrors) > 0 {
result.WriteString("\n\nParameter errors:\n")
// Sort errors for deterministic output
paramErrs := make([]string, 0, len(allParamErrors))
for errMsg := range allParamErrors {
paramErrs = append(paramErrs, errMsg)
}
sort.Strings(paramErrs)
for _, errMsg := range paramErrs {
result.WriteString(" " + errMsg + "\n")
}
}
if len(allTemplateErrors) > 0 {
result.WriteString("\n\nTemplate errors:\n")
templateErrs := make([]string, 0, len(allTemplateErrors))
for errMsg := range allTemplateErrors {
templateErrs = append(templateErrs, errMsg)
}
sort.Strings(templateErrs)
for _, errMsg := range templateErrs {
result.WriteString(" " + errMsg + "\n")
}
}
return fmt.Errorf("%s", strings.TrimRight(result.String(), "\n"))
}