Feat: add vela def gen-cue command (#5956)

* Feat: add vela def gen-cue command

Signed-off-by: iyear <ljyngup@gmail.com>

* Fix: golangci-lint G304 error

Signed-off-by: iyear <ljyngup@gmail.com>

* Chore: remove useless stat

Signed-off-by: iyear <ljyngup@gmail.com>

* Chore: remove useless log

Signed-off-by: iyear <ljyngup@gmail.com>

* Chore: type alias

Signed-off-by: iyear <ljyngup@gmail.com>

---------

Signed-off-by: iyear <ljyngup@gmail.com>
This commit is contained in:
iyear
2023-05-05 19:26:13 +08:00
committed by GitHub
parent e828d3c8cf
commit e675cdafc4
5 changed files with 119 additions and 5 deletions

2
go.mod
View File

@@ -85,6 +85,7 @@ require (
github.com/wonderflow/cert-manager-api v1.0.4-0.20210304051430-e08aa76f6c5f
github.com/xanzy/go-gitlab v0.83.0
github.com/xlab/treeprint v1.2.0
go.uber.org/multierr v1.7.0
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.6.0
golang.org/x/oauth2 v0.6.0
@@ -287,7 +288,6 @@ require (
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect

View File

@@ -38,6 +38,7 @@ import (
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"go.uber.org/multierr"
"gopkg.in/yaml.v3"
errors2 "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -57,6 +58,8 @@ import (
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/filters"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/cuegen"
providergen "github.com/oam-dev/kubevela/references/cuegen/generators/provider"
)
const (
@@ -89,6 +92,7 @@ func DefinitionCommandGroup(c common.Args, order string, ioStreams util.IOStream
NewDefinitionGenDocCommand(c, ioStreams),
NewCapabilityShowCommand(c, "", ioStreams),
NewDefinitionGenAPICommand(c),
NewDefinitionGenCUECommand(c),
)
return cmd
}
@@ -1138,3 +1142,72 @@ func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
return cmd
}
// NewDefinitionGenCUECommand create the `vela def gen-cue` command to help user generate CUE schema from the go code
func NewDefinitionGenCUECommand(_ common.Args) *cobra.Command {
const (
typeProvider = "provider"
)
var (
typ string
output string
typeMap map[string]string
nullable bool
)
cmd := &cobra.Command{
Use: "gen-cue [flags] SOURCE.go",
Args: cobra.ExactArgs(1),
Short: "Generate CUE schema from Go code.",
Long: "Generate CUE schema from Go code.\n" +
"* This command provide a way to generate CUE schema from Go code,\n" +
"* Which can be used to keep consistency between Go code and CUE schema automatically.\n",
Example: "# Generate CUE schema for provider type\n" +
"> vela def gen-cue -t provider /path/to/myprovider.go\n" +
"# Generate CUE schema for provider type with custom types\n" +
"> vela def gen-cue -t provider --types *k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis /path/to/myprovider.go\n" +
"# Generate CUE schema for provider type with custom output path\n" +
"> vela def gen-cue -t provider -o /path/to/myprovider.cue /path/to/myprovider.go\n",
RunE: func(cmd *cobra.Command, args []string) (rerr error) {
// convert map[string]string to map[string]cuegen.Type
newTypeMap := make(map[string]cuegen.Type, len(typeMap))
for k, v := range typeMap {
newTypeMap[k] = cuegen.Type(v)
}
file := args[0]
if !strings.HasSuffix(file, ".go") {
return fmt.Errorf("invalid file %s, must be a go file", file)
}
if output == "" {
output = strings.TrimSuffix(file, filepath.Ext(file)) + ".cue"
}
f, err := os.Create(filepath.Clean(output))
if err != nil {
return err
}
defer multierr.AppendInvoke(&rerr, multierr.Close(f))
switch typ {
case typeProvider:
return providergen.Generate(providergen.Options{
File: file,
Writer: f,
Types: newTypeMap,
Nullable: nullable,
})
default:
return fmt.Errorf("invalid type %s", typ)
}
},
}
cmd.Flags().StringVarP(&typ, "type", "t", "", "Type of the definition to generate. Valid types: [provider]")
cmd.Flags().BoolVar(&nullable, "nullable", false, "Whether to generate null enum for pointer type")
cmd.Flags().StringVarP(&output, "output", "o", "", "Output CUE file path, if not specified, the CUE file will be generated in the same directory")
cmd.Flags().StringToStringVar(&typeMap, "types", map[string]string{}, "Special types to generate, format: <package+struct>=[any|ellipsis]. e.g. --types=*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis")
return cmd
}

View File

@@ -179,6 +179,22 @@ func removeDir(dirname string, t *testing.T) {
}
}
func compareFile(t *testing.T, got, expected string) {
gotBytes, err := os.ReadFile(got)
if err != nil {
t.Fatalf("failed to read file %s: %v", got, err)
}
expectedBytes, err := os.ReadFile(expected)
if err != nil {
t.Fatalf("failed to read file %s: %v", expected, err)
}
if !bytes.Equal(gotBytes, expectedBytes) {
t.Errorf("got %s, expected %s", gotBytes, expectedBytes)
}
}
func TestNewDefinitionCommandGroup(t *testing.T) {
cmd := DefinitionCommandGroup(common2.Args{}, "", util.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
initCommand(cmd)
@@ -655,3 +671,28 @@ func TestNewDefinitionGenAPICommand(t *testing.T) {
t.Fatalf("unexpeced error when executing genapi command: %v", err)
}
}
func TestNewDefinitionGenCUECommand(t *testing.T) {
c := initArgs()
cmd := NewDefinitionGenCUECommand(c)
initCommand(cmd)
// re-use the provider testdata
providerPath := "../cuegen/generators/provider/testdata"
output := filepath.Join(t.TempDir(), "output.cue")
expected := filepath.Join(providerPath, "valid.cue")
cmd.SetArgs([]string{
"-t", "provider",
"-o", output,
"--types", "*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis",
"--types", "*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.UnstructuredList=ellipsis",
filepath.Join(providerPath, "valid.go"),
})
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpeced error when executing gencue command: %v", err)
}
compareFile(t, output, expected)
}

View File

@@ -88,7 +88,7 @@ func (g *Generator) convert(typ gotypes.Type) (cueast.Expr, error) {
case TypeEllipsis:
return &cueast.StructLit{Elts: []cueast.Decl{&cueast.Ellipsis{}}}, nil
default:
return nil, fmt.Errorf("unsupported special cue type %d", t)
return nil, fmt.Errorf("unsupported special cue type: %v", t)
}
}

View File

@@ -19,13 +19,13 @@ package cuegen
import goast "go/ast"
// Type is a special cue type
type Type int
type Type string
const (
// TypeAny converts go type to _(top value) in cue
TypeAny Type = iota
TypeAny Type = "any"
// TypeEllipsis converts go type to {...} in cue
TypeEllipsis
TypeEllipsis Type = "ellipsis"
)
type options struct {