mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 10:00:06 +00:00
Feat: 7024 Enable custom errors in components similar to traits (#7028)
Some checks failed
Webhook Upgrade Validation / webhook-upgrade-check (push) Failing after 12m42s
Some checks failed
Webhook Upgrade Validation / webhook-upgrade-check (push) Failing after 12m42s
Signed-off-by: Brian Kane <briankane1@gmail.com>
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue/definition/health"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
@@ -130,11 +131,35 @@ func (wd *workloadDef) Complete(ctx process.Context, abstractTemplate string, pa
|
||||
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
|
||||
var userErrors []string
|
||||
if errs := val.LookupPath(value.FieldPath(ErrsFieldName)); errs.Exists() {
|
||||
if err := errs.Decode(&userErrors); err != nil {
|
||||
klog.Warningf("Workload definition '%s' has malformed 'errs' field (expected []string): %v. Custom error reporting will be skipped.", wd.name, err)
|
||||
}
|
||||
return fmt.Errorf("validation failed for workload %s: %w", wd.name, err)
|
||||
}
|
||||
|
||||
validationErr := val.Validate()
|
||||
|
||||
if validationErr != nil || len(userErrors) > 0 {
|
||||
var result strings.Builder
|
||||
result.WriteString(fmt.Sprintf("validation failed for workload %s:", wd.name))
|
||||
|
||||
if len(userErrors) > 0 {
|
||||
result.WriteString("\n\nUser Errors:\n")
|
||||
for _, e := range userErrors {
|
||||
result.WriteString(fmt.Sprintf(" %s\n", e))
|
||||
}
|
||||
}
|
||||
|
||||
if validationErr != nil {
|
||||
if fmtErr := FormatCUEError(validationErr, "validation failed for", "workload", wd.name, &val); fmtErr != nil {
|
||||
errMsg := fmtErr.Error()
|
||||
errMsg = strings.TrimPrefix(errMsg, fmt.Sprintf("validation failed for workload %s:", wd.name))
|
||||
result.WriteString(errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New(strings.TrimRight(result.String(), "\n"))
|
||||
}
|
||||
output := val.LookupPath(value.FieldPath(OutputFieldName))
|
||||
|
||||
@@ -290,11 +315,35 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string, param
|
||||
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
|
||||
var userErrors []string
|
||||
if errs := val.LookupPath(value.FieldPath(ErrsFieldName)); errs.Exists() {
|
||||
if err := errs.Decode(&userErrors); err != nil {
|
||||
klog.Warningf("Trait definition '%s' has malformed 'errs' field (expected []string): %v. Custom error reporting will be skipped.", td.name, err)
|
||||
}
|
||||
return fmt.Errorf("validation failed for trait %s: %w", td.name, err)
|
||||
}
|
||||
|
||||
validationErr := val.Validate()
|
||||
|
||||
if validationErr != nil || len(userErrors) > 0 {
|
||||
var result strings.Builder
|
||||
result.WriteString(fmt.Sprintf("validation failed for trait %s:", td.name))
|
||||
|
||||
if len(userErrors) > 0 {
|
||||
result.WriteString("\n\nUser Errors:\n")
|
||||
for _, e := range userErrors {
|
||||
result.WriteString(fmt.Sprintf(" %s\n", e))
|
||||
}
|
||||
}
|
||||
|
||||
if validationErr != nil {
|
||||
if fmtErr := FormatCUEError(validationErr, "validation failed for", "trait", td.name, &val); fmtErr != nil {
|
||||
errMsg := fmtErr.Error()
|
||||
errMsg = strings.TrimPrefix(errMsg, fmt.Sprintf("validation failed for trait %s:", td.name))
|
||||
result.WriteString(errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New(strings.TrimRight(result.String(), "\n"))
|
||||
}
|
||||
|
||||
processing := val.LookupPath(value.FieldPath("processing"))
|
||||
@@ -348,13 +397,6 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string, param
|
||||
}
|
||||
}
|
||||
|
||||
errs := val.LookupPath(value.FieldPath(ErrsFieldName))
|
||||
if errs.Exists() {
|
||||
if err := parseErrors(errs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -408,17 +450,6 @@ func injectOutputStatusIntoBaseContext(ctx process.Context, c string, statusByte
|
||||
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{}
|
||||
@@ -535,13 +566,10 @@ func getResourceFromObj(ctx context.Context, pctx process.Context, obj *unstruct
|
||||
|
||||
// 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)
|
||||
|
||||
if err != nil {
|
||||
errList := cueerrors.Errors(err)
|
||||
for _, e := range errList {
|
||||
errMsg := e.Error()
|
||||
@@ -552,7 +580,6 @@ func FormatCUEError(err error, messagePrefix string, entityType, entityName stri
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -566,6 +593,7 @@ func FormatCUEError(err error, messagePrefix string, entityType, entityName stri
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(allParamErrors) == 0 && len(allTemplateErrors) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -240,6 +240,39 @@ output:{
|
||||
"kind": "Ingress",
|
||||
}},
|
||||
},
|
||||
"using errs field in workload": {
|
||||
workloadTemplate: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: context.name
|
||||
}
|
||||
errs: parameter.errs
|
||||
parameter: { errs: [...string] }`,
|
||||
params: map[string]interface{}{
|
||||
"errs": []string{"custom workload error"},
|
||||
},
|
||||
hasCompileErr: true,
|
||||
},
|
||||
"user errors and validation errors together": {
|
||||
workloadTemplate: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: context.name
|
||||
spec: replicas: parameter.replicas
|
||||
}
|
||||
errs: (if parameter.replicas < 1 {["replicas must be at least 1"]} else {[]})
|
||||
parameter: {
|
||||
replicas: int
|
||||
required: string // This will cause a validation error
|
||||
}`,
|
||||
params: map[string]interface{}{
|
||||
"replicas": 0,
|
||||
// missing "required" field
|
||||
},
|
||||
hasCompileErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range testCases {
|
||||
|
||||
Reference in New Issue
Block a user