Convert SDK to the new options API (#238)
This commit is contained in:
1 parent
b5acf6afdf
commit
908a299ea3
162 files changed
+6440
-5891
No files matched your search
+92
-274
@@ -1,286 +1,104 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
_struct "github.com/golang/protobuf/ptypes/struct"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/gen/controller/protooptions"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
)
|
||||
|
||||
var regex = regexp.MustCompile(`(json:".*")`)
|
||||
|
||||
type visitFn func(node ast.Node)
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
fn(node)
|
||||
return fn
|
||||
func printDebug(desc protoreflect.MessageDescriptor) {
|
||||
fmt.Printf("Evaluating: %q\n", desc.FullName())
|
||||
fmt.Printf("Name: %q\n", strcase.ToCamel(string(desc.Name())))
|
||||
fmt.Printf("ProtoName: %q\n", desc.Name())
|
||||
fmt.Printf("Double Parent: %q\n", desc.FullName().Parent().Parent().Name())
|
||||
fmt.Printf("Field Descriptors:\n")
|
||||
for i := 0; i < desc.Fields().Len(); i++ {
|
||||
fieldDesc := desc.Fields().Get(i)
|
||||
fmt.Printf(" field %d: %#v\n", i, fieldDesc)
|
||||
fieldDesc.Options()
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func parsePBs() {
|
||||
for _, inputStruct := range inputStructs {
|
||||
inFile, err := filepath.Abs(inputStruct.inFile)
|
||||
if err != nil {
|
||||
fmt.Printf("error opening file %q: %v\n", inputStruct.inFile, err)
|
||||
os.Exit(1)
|
||||
for _, in := range inputStructs {
|
||||
msg := in.inProto.ProtoReflect()
|
||||
desc := msg.Descriptor()
|
||||
|
||||
//printDebug(desc)
|
||||
|
||||
// Evaluate above, populate below.
|
||||
in.generatedStructure.pkg = packageFromFullName(desc.FullName())
|
||||
in.generatedStructure.name = string(desc.Name())
|
||||
for i := 0; i < desc.Fields().Len(); i++ {
|
||||
fd := desc.Fields().Get(i)
|
||||
fi := fieldInfo{
|
||||
Name: strcase.ToCamel(string(fd.Name())),
|
||||
ProtoName: string(fd.Name()),
|
||||
}
|
||||
// Adjust for slices
|
||||
sliceText := ""
|
||||
if fd.Cardinality() == protoreflect.Repeated {
|
||||
sliceText = "[]"
|
||||
}
|
||||
// Add generate option info
|
||||
opts := fd.Options().(*descriptorpb.FieldOptions)
|
||||
if proto.GetExtension(opts, protooptions.E_GenerateSdkOption).(bool) {
|
||||
fi.GenerateSdkOption = true
|
||||
}
|
||||
switch k := fd.Kind(); k {
|
||||
case protoreflect.MessageKind:
|
||||
ptr, pkg, name := messageKind(fd)
|
||||
if pkg != "" && pkg != in.generatedStructure.pkg {
|
||||
name = fmt.Sprintf("%s.%s", pkg, name)
|
||||
}
|
||||
fi.FieldType = sliceText + ptr + name
|
||||
default:
|
||||
fi.FieldType = sliceText + k.String()
|
||||
}
|
||||
in.generatedStructure.fields = append(in.generatedStructure.fields, fi)
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
inAst, err := parser.ParseFile(fset, inFile, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
fmt.Printf("error parsing %s: %v\n", inFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
inputStruct.nameJsonMap = make(map[string]string)
|
||||
ast.Walk(visitFn(func(n ast.Node) {
|
||||
spec, ok := n.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if spec.Name == nil {
|
||||
return
|
||||
}
|
||||
if spec.Name.Name != inputStruct.inName {
|
||||
return
|
||||
}
|
||||
|
||||
spec.Name.Name = inputStruct.outName
|
||||
st, ok := spec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
fmt.Printf("expected struct type for identifier, got %t\n", spec.Type)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
if st.Fields.List == nil {
|
||||
fmt.Printf("no fields found in %q\n", inputStruct.inName)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
// Tee up unexported field deletion
|
||||
var elideList []int
|
||||
{
|
||||
defer func() {
|
||||
var cutCount int
|
||||
// Remove unexported proto stuff
|
||||
for _, val := range elideList {
|
||||
loc := val - cutCount
|
||||
st.Fields.List = append(st.Fields.List[:loc], st.Fields.List[loc+1:]...)
|
||||
cutCount++
|
||||
}
|
||||
|
||||
// Add default fields if a base resource
|
||||
if inputStruct.templateType == templateTypeResource && !inputStruct.outputOnly {
|
||||
st.Fields.List = append([]*ast.Field{{
|
||||
Names: []*ast.Ident{
|
||||
{
|
||||
Name: "defaultFields",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "defaultFields",
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.ArrayType{
|
||||
Elt: &ast.Ident{
|
||||
Name: "string",
|
||||
},
|
||||
},
|
||||
}}, st.Fields.List...)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i, field := range st.Fields.List {
|
||||
if !field.Names[0].IsExported() {
|
||||
elideList = append(elideList, i)
|
||||
continue
|
||||
}
|
||||
|
||||
// Anything that isn't a basic type we expect to be a star
|
||||
// expression with a selector; that is, a wrapper value, timestamp
|
||||
// value, etc.
|
||||
//
|
||||
// TODO: this isn't necessarily a good assumption, which means we
|
||||
// might get failures with other types. This is an internal tools
|
||||
// only; we can revisit as needed!
|
||||
var selectorExpr *ast.SelectorExpr
|
||||
switch typ := field.Type.(type) {
|
||||
case *ast.Ident:
|
||||
goto TAGMODIFY
|
||||
case *ast.ArrayType:
|
||||
goto TAGMODIFY
|
||||
case *ast.StarExpr:
|
||||
switch nextTyp := typ.X.(type) {
|
||||
case *ast.Ident:
|
||||
// Already a pointer, don't do anything
|
||||
if nextTyp.Name == "ScopeInfo" {
|
||||
st.Fields.List[i] = &ast.Field{
|
||||
Names: field.Names,
|
||||
Type: &ast.Ident{
|
||||
Name: "info.Scope",
|
||||
},
|
||||
Tag: field.Tag,
|
||||
}
|
||||
}
|
||||
goto TAGMODIFY
|
||||
case *ast.SelectorExpr:
|
||||
selectorExpr = nextTyp
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
selectorExpr = typ
|
||||
}
|
||||
|
||||
switch {
|
||||
case selectorExpr != nil:
|
||||
xident, ok := selectorExpr.X.(*ast.Ident)
|
||||
if !ok {
|
||||
fmt.Printf("unexpected non-ident type in selector\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch xident.Name {
|
||||
case "wrappers":
|
||||
switch selectorExpr.Sel.Name {
|
||||
case "StringValue":
|
||||
st.Fields.List[i] = &ast.Field{
|
||||
Names: field.Names,
|
||||
Type: &ast.Ident{
|
||||
Name: "*string",
|
||||
},
|
||||
Tag: field.Tag,
|
||||
}
|
||||
case "BoolValue":
|
||||
st.Fields.List[i] = &ast.Field{
|
||||
Names: field.Names,
|
||||
Type: &ast.Ident{
|
||||
Name: "*bool",
|
||||
},
|
||||
Tag: field.Tag,
|
||||
}
|
||||
case "Int64Value":
|
||||
st.Fields.List[i] = &ast.Field{
|
||||
Names: field.Names,
|
||||
Type: &ast.Ident{
|
||||
Name: "*int64",
|
||||
},
|
||||
Tag: field.Tag,
|
||||
}
|
||||
default:
|
||||
fmt.Printf("unhandled wrappers selector sel name %q\n", selectorExpr.Sel.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "timestamp":
|
||||
switch selectorExpr.Sel.Name {
|
||||
case "Timestamp":
|
||||
st.Fields.List[i] = &ast.Field{
|
||||
Names: field.Names,
|
||||
Type: &ast.Ident{
|
||||
Name: "time.Time",
|
||||
},
|
||||
Tag: field.Tag,
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Printf("unhandled timestamp selector sel name %q\n", selectorExpr.Sel.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "scopes":
|
||||
switch selectorExpr.Sel.Name {
|
||||
case "ScopeInfo":
|
||||
st.Fields.List[i] = &ast.Field{
|
||||
Names: field.Names,
|
||||
Type: &ast.Ident{
|
||||
Name: "info.Scope",
|
||||
},
|
||||
Tag: field.Tag,
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Printf("unhandled scopes selector sel name %q\n", selectorExpr.Sel.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "_struct":
|
||||
switch selectorExpr.Sel.Name {
|
||||
case "Struct":
|
||||
st.Fields.List[i] = &ast.Field{
|
||||
Names: field.Names,
|
||||
Type: &ast.MapType{
|
||||
Key: &ast.Ident{
|
||||
Name: "string",
|
||||
},
|
||||
Value: &ast.InterfaceType{
|
||||
Methods: &ast.FieldList{},
|
||||
},
|
||||
},
|
||||
Tag: field.Tag,
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Printf("unhandled timestamp selector sel name %q\n", selectorExpr.Sel.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Printf("unhandled xident name %q\n", xident.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Println("unhandled non-ident, non-selector case")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
TAGMODIFY:
|
||||
tagString := regex.FindString(st.Fields.List[i].Tag.Value)
|
||||
st.Fields.List[i].Tag.Value = "`" + tagString + "`"
|
||||
tagString = strings.TrimSuffix(strings.TrimPrefix(tagString, `json:"`), `,omitempty"`)
|
||||
inputStruct.nameJsonMap[strings.ToLower(field.Names[0].Name)] = tagString
|
||||
}
|
||||
}), inAst)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := format.Node(buf, fset, inAst); err != nil {
|
||||
fmt.Printf("error formatting new code: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// We have to manually cut out the lines we don't want because comments
|
||||
// won't be preserved otherwise. See the note about lossy comments in
|
||||
// https://arslan.io/2017/09/14/the-ultimate-guide-to-writing-a-go-tool/
|
||||
scanner := bufio.NewScanner(bytes.NewBufferString(buf.String()))
|
||||
var inType bool
|
||||
var outBuf []string
|
||||
for scanner.Scan() {
|
||||
if !inType {
|
||||
if strings.HasPrefix(scanner.Text(), "type "+inputStruct.outName+" struct") {
|
||||
inType = true
|
||||
// Don't add this line, we'll do it in the template
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if scanner.Text() == "}" {
|
||||
// We've reached the end of the type
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if inType {
|
||||
outBuf = append(outBuf, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
inputStruct.structFields = strings.Join(outBuf, "\n")
|
||||
//fmt.Printf("Parsed: %#v\n", in.generatedStructure)
|
||||
}
|
||||
}
|
||||
|
||||
func packageFromFullName(fullName protoreflect.FullName) string {
|
||||
// Example full name: controller.api.resources.groups.v1.Group
|
||||
// Crawling up the parent twice jumps back past v1.Group placing us at "groups".
|
||||
pkgName := fullName.Parent().Parent().Name()
|
||||
if pkgName.IsValid() {
|
||||
return string(pkgName)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
stringValueName = (&wrapperspb.StringValue{}).ProtoReflect().Descriptor().FullName()
|
||||
boolValueName = (&wrapperspb.BoolValue{}).ProtoReflect().Descriptor().FullName()
|
||||
structValueName = (&_struct.Struct{}).ProtoReflect().Descriptor().FullName()
|
||||
timestampName = (×tamppb.Timestamp{}).ProtoReflect().Descriptor().FullName()
|
||||
)
|
||||
|
||||
func messageKind(fd protoreflect.FieldDescriptor) (ptr, pkg, name string) {
|
||||
switch fd.Message().FullName() {
|
||||
case stringValueName:
|
||||
return "", "", "string"
|
||||
case boolValueName:
|
||||
return "", "", "bool"
|
||||
case structValueName:
|
||||
return "", "", "map[string]interface{}"
|
||||
case timestampName:
|
||||
return "", "time", "Time"
|
||||
default:
|
||||
return "*", packageFromFullName(fd.Message().FullName()), string(fd.Message().Name())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user