This is an automated email from the ASF dual-hosted git repository.
zhongxjian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/master by this push:
new b4a0618d [operator] Add validate command logic v3
b4a0618d is described below
commit b4a0618d0d7007b85760af5089dd16b84a7dca8f
Author: mfordjody <[email protected]>
AuthorDate: Thu Dec 26 11:11:22 2024 +0800
[operator] Add validate command logic v3
---
dubboctl/pkg/validate/validate.go | 143 ++++++++++++++++++++++++-----
operator/pkg/apis/validation/validation.go | 62 +++++++++++++
2 files changed, 182 insertions(+), 23 deletions(-)
diff --git a/dubboctl/pkg/validate/validate.go
b/dubboctl/pkg/validate/validate.go
index 48b6536d..bc7c6750 100644
--- a/dubboctl/pkg/validate/validate.go
+++ b/dubboctl/pkg/validate/validate.go
@@ -5,22 +5,23 @@ import (
"errors"
"fmt"
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
- "github.com/apache/dubbo-kubernetes/operator/cmd/validation"
operator "github.com/apache/dubbo-kubernetes/operator/pkg/apis"
- "github.com/apache/dubbo-kubernetes/operator/pkg/config"
+ operatorvalidate
"github.com/apache/dubbo-kubernetes/operator/pkg/apis/validation"
+ "github.com/apache/dubbo-kubernetes/pkg/config/validation"
+ "github.com/apache/dubbo-kubernetes/pkg/util/slices"
"github.com/hashicorp/go-multierror"
"github.com/spf13/cobra"
"io"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
+ "os"
+ "path/filepath"
+ "strings"
)
var (
errFiles = errors.New(`error: you must specify resources by --filename.
-Example resource specifications include:
- '-f default.yaml'
- '--filename=default.json'`)
-
+Example resource specifications include: '-f default.yaml'`)
validFields = map[string]struct{}{
"apiVersion": {},
"kind": {},
@@ -28,14 +29,15 @@ Example resource specifications include:
"spec": {},
"status": {},
}
+ fileExtensions = []string{".json", ".yaml", ".yml"}
)
type validator struct{}
-func (v *validator) validateFile(path string, dubboNamespace *string,
defaultNamespace string, reader io.Reader, writer io.Writer)
(validation.Warning, error) {
+func (v *validator) validateFile(path string, dubboNamespace *string, reader
io.Reader, writer io.Writer) (validation.Warning, error) {
yamlReader := yaml.NewYAMLReader(bufio.NewReader(reader))
var errs error
- var warnings validation.Warnings
+ var warnings validation.Warning
for {
doc, err := yamlReader.Read()
if err == io.EOF {
@@ -66,28 +68,22 @@ func (v *validator) validateFile(path string,
dubboNamespace *string, defaultNam
}
}
-func (v *validator) validateResource(istioNamespace string, un
*unstructured.Unstructured, writer io.Writer) (validation.Warnings, error) {
- g := config.GroupVersionKind{
- Group: un.GroupVersionKind().Group,
- Version: un.GroupVersionKind().Version,
- Kind: un.GroupVersionKind().Kind,
- }
+func (v *validator) validateResource(dubboNamespace string, un
*unstructured.Unstructured, writer io.Writer) (validation.Warning, error) {
var errs error
if errs != nil {
return nil, errs
}
-
if un.GetAPIVersion() ==
operator.DubboOperatorGVK.GroupVersion().String() {
if un.GetKind() == operator.DubboOperatorGVK.Kind {
if err := checkFields(un); err != nil {
return nil, err
}
- warnings, err :=
operatorvalidate.ParseAndValidateIstioOperator(un.Object, nil)
+ warnings, err :=
operatorvalidate.ParseAndValidateDubboOperator(un.Object, nil)
if err != nil {
return nil, err
}
if len(warnings) > 0 {
- return validation.Warning(warnings.ToError()),
nil
+ return validation.Warning(warnings.ToErrors()),
nil
}
}
}
@@ -105,6 +101,7 @@ func checkFields(un *unstructured.Unstructured) error {
}
func NewValidateCommand(ctx cli.Context) *cobra.Command {
+ var files []string
vc := &cobra.Command{
Use: "validate -f FILENAME [options]",
Short: "Validate Dubbo rules files",
@@ -115,11 +112,12 @@ func NewValidateCommand(ctx cli.Context) *cobra.Command {
# Validate current services under 'default' namespace with in the cluster
kubectl get services -o yaml | dubboctl validate -f -
`,
- Args: cobra.NoArgs,
- Aliases: []string{"v"},
+ Args: cobra.NoArgs,
+ Aliases: []string{"v"},
+ SilenceUsage: true,
RunE: func(cmd *cobra.Command, _ []string) error {
dn := ctx.DubboNamespace()
- return validateFiles(&dn, nil, nil)
+ return validateFiles(&dn, files, cmd.OutOrStderr())
},
}
return vc
@@ -132,7 +130,7 @@ func validateFiles(dubboNamespace *string, files []string,
writer io.Writer) err
v := &validator{}
var errs error
var reader io.ReadCloser
-
+ warningsByFilename := map[string]validation.Warning{}
processFile := func(path string) {
var err error
if path == "-" {
@@ -144,15 +142,114 @@ func validateFiles(dubboNamespace *string, files
[]string, writer io.Writer) err
return
}
}
- warning, err := v.validateFile(path, istioNamespace,
defaultNamespace, reader, writer)
+ warning, err := v.validateFile(path, dubboNamespace, reader,
writer)
if err != nil {
errs = multierror.Append(errs, err)
}
err = reader.Close()
if err != nil {
- log.Infof("file: %s is not closed: %v", path, err)
+ fmt.Printf("file: %s is not closed: %v", path, err)
}
warningsByFilename[path] = warning
}
+ processDirectory := func(directory string, processFile func(string))
error {
+ err := filepath.Walk(directory, func(path string, info
os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if info.IsDir() {
+ return nil
+ }
+
+ if isFileFormatValid(path) {
+ processFile(path)
+ }
+
+ return nil
+ })
+ return err
+ }
+
+ processedFiles := map[string]bool{}
+ for _, filename := range files {
+ var isDir bool
+ if filename != "-" {
+ fi, err := os.Stat(filename)
+ if err != nil {
+ errs = multierror.Append(errs,
fmt.Errorf("cannot stat file %q: %v", filename, err))
+ continue
+ }
+ isDir = fi.IsDir()
+ }
+
+ if !isDir {
+ processFile(filename)
+ processedFiles[filename] = true
+ continue
+ }
+ if err := processDirectory(filename, func(path string) {
+ processFile(path)
+ processedFiles[path] = true
+ }); err != nil {
+ errs = multierror.Append(errs, err)
+ }
+ }
+ files = []string{}
+ for p := range processedFiles {
+ files = append(files, p)
+ }
+
+ if errs != nil {
+ for _, fname := range files {
+ if w := warningsByFilename[fname]; w != nil {
+ if fname == "-" {
+ _, _ = fmt.Fprint(writer,
warningToString(w))
+ break
+ }
+ _, _ = fmt.Fprintf(writer, "%q has warnings:
%v\n", fname, warningToString(w))
+ }
+ }
+ return errs
+ }
+ for _, fname := range files {
+ if fname == "-" {
+ if w := warningsByFilename[fname]; w != nil {
+ _, _ = fmt.Fprint(writer, warningToString(w))
+ } else {
+ _, _ = fmt.Fprintf(writer, "validation
succeed\n")
+ }
+ break
+ }
+
+ if w := warningsByFilename[fname]; w != nil {
+ _, _ = fmt.Fprintf(writer, "%q has warnings: %v\n",
fname, warningToString(w))
+ } else {
+ _, _ = fmt.Fprintf(writer, "%q is valid\n", fname)
+ }
+ }
+
return nil
}
+
+func warningToString(w validation.Warning) string {
+ we, ok := w.(*multierror.Error)
+ if ok {
+ we.ErrorFormat = func(i []error) string {
+ points := make([]string, len(i))
+ for i, err := range i {
+ points[i] = fmt.Sprintf("* %s", err)
+ }
+
+ return fmt.Sprintf(
+ "\n\t%s\n",
+ strings.Join(points, "\n\t"))
+ }
+ }
+ return w.Error()
+}
+
+func isFileFormatValid(file string) bool {
+ ext := filepath.Ext(file)
+ return slices.Contains(fileExtensions, ext)
+}
diff --git a/operator/pkg/apis/validation/validation.go
b/operator/pkg/apis/validation/validation.go
new file mode 100644
index 00000000..3922a98c
--- /dev/null
+++ b/operator/pkg/apis/validation/validation.go
@@ -0,0 +1,62 @@
+package validation
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "github.com/apache/dubbo-kubernetes/operator/pkg/apis"
+ "github.com/apache/dubbo-kubernetes/operator/pkg/util"
+ "github.com/apache/dubbo-kubernetes/operator/pkg/values"
+ "github.com/apache/dubbo-kubernetes/pkg/kube"
+ "sigs.k8s.io/yaml"
+)
+
+type Warnings = util.Errors
+
+func ParseAndValidateDubboOperator(dopm values.Map, client kube.CLIClient)
(Warnings, util.Errors) {
+ iop := &apis.DubboOperator{}
+ dec := json.NewDecoder(bytes.NewBufferString(dopm.JSON()))
+ dec.DisallowUnknownFields()
+ if err := dec.Decode(iop); err != nil {
+ return nil, util.NewErrs(fmt.Errorf("could not unmarshal: %v",
err))
+ }
+ var warnings Warnings
+ var errors util.Errors
+
+ vw, ve := validateValues(iop)
+ warnings = util.AppendErrs(warnings, vw)
+ errors = util.AppendErrs(errors, ve)
+ errors = util.AppendErr(errors,
validateComponentNames(iop.Spec.Components))
+ return warnings, errors
+}
+
+type FeatureValidator func(*apis.Values, apis.DubboOperatorSpec) (Warnings,
util.Errors)
+
+func validateFeatures(values *apis.Values, spec apis.DubboOperatorSpec)
(Warnings, util.Errors) {
+ validators := []FeatureValidator{}
+ var warnings Warnings
+ var errs util.Errors
+ for _, validator := range validators {
+ newWarnings, newErrs := validator(values, spec)
+ errs = util.AppendErrs(errs, newErrs)
+ warnings = append(warnings, newWarnings...)
+ }
+ return warnings, errs
+}
+
+func validateValues(raw *apis.DubboOperator) (Warnings, util.Errors) {
+ values := &apis.Values{}
+ if err := yaml.Unmarshal(raw.Spec.Values, values); err != nil {
+ return nil, util.NewErrs(fmt.Errorf("could not unmarshal: %v",
err))
+ }
+ warnings, errs := validateFeatures(values, raw.Spec)
+
+ return warnings, errs
+}
+
+func validateComponentNames(components *apis.DubboComponentSpec) error {
+ if components == nil {
+ return nil
+ }
+ return nil
+}