Skip to content

Commit f2fac7e

Browse files
hooking up native command maybe (#413)
* hooking up native command maybe * Bad name for now * finding my bearings * does this actually work? * remove silly comments * native lang kind of works on accident * with tests * ... does this actually work? * ok that is the other code path.. * does this work? * verify that two structs work * oops --------- Co-authored-by: lekko-app[bot] <108442683+lekko-app[bot]@users.noreply.github.com>
1 parent 2e47c6d commit f2fac7e

File tree

12 files changed

+1056
-66
lines changed

12 files changed

+1056
-66
lines changed

cmd/lekko/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ func main() {
120120
mergeFileCmd(),
121121
diffCmd(),
122122
confCmd(),
123+
convertLangCmd(),
123124
)
124125

125126
logging.InitColors()

cmd/lekko/sync.go

Lines changed: 281 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"os/exec"
2323
"path"
2424
"path/filepath"
25+
"strings"
2526

2627
"golang.org/x/mod/modfile"
2728

@@ -31,6 +32,8 @@ import (
3132
"github.com/spf13/cobra"
3233
"google.golang.org/protobuf/encoding/protojson"
3334
"google.golang.org/protobuf/proto"
35+
"google.golang.org/protobuf/reflect/protodesc"
36+
"google.golang.org/protobuf/reflect/protoreflect"
3437
"google.golang.org/protobuf/reflect/protoregistry"
3538
"google.golang.org/protobuf/types/descriptorpb"
3639
"google.golang.org/protobuf/types/known/anypb"
@@ -87,7 +90,11 @@ func syncGoCmd() *cobra.Command {
8790
if err != nil {
8891
return err
8992
}
90-
return syncer.Sync(ctx, r)
93+
err = syncer.Sync(ctx, r)
94+
if err != nil {
95+
return err
96+
}
97+
return WriteToRepo(ctx, r, syncer.TypeRegistry)
9198
},
9299
}
93100
cmd.Flags().StringVarP(&repoPath, "repo-path", "r", "", "path to config repository, will use autodetect if not set")
@@ -451,3 +458,276 @@ func GetRegistryFromFileDescriptorSet(fds *descriptorpb.FileDescriptorSet) (*pro
451458
}
452459
return st.Types, nil
453460
}
461+
462+
/*
463+
* Convert config from one language to another. This can also be useful for converting to the same language to normalize the syntax.
464+
*
465+
* This handles proto change through go fly a kite
466+
*/
467+
func convertLangCmd() *cobra.Command {
468+
var inputLang, outputLang, inputFile string
469+
cmd := &cobra.Command{
470+
Use: "convert",
471+
Short: "convert",
472+
Hidden: true,
473+
RunE: func(cmd *cobra.Command, args []string) error {
474+
ctx := cmd.Context()
475+
// TODO validate input (is this not built in??)
476+
f, err := os.ReadFile(inputFile)
477+
if err != nil {
478+
panic(err)
479+
}
480+
privateFile := goToGo(ctx, f)
481+
fmt.Println(privateFile)
482+
return nil
483+
},
484+
}
485+
cmd.Flags().StringVarP(&inputLang, "input-language", "i", "ts", "go, ts, starlark, proto, proto-json")
486+
cmd.Flags().StringVarP(&inputFile, "input-file", "I", "/dev/stdin", "input file")
487+
cmd.Flags().StringVarP(&outputLang, "output-language", "o", "ts", "go, ts, starlark, proto, proto-json")
488+
return cmd
489+
}
490+
491+
func goToGo(ctx context.Context, f []byte) string {
492+
registry, err := prototypes.RegisterDynamicTypes(nil)
493+
if err != nil {
494+
panic(err)
495+
}
496+
syncer := sync.NewGoSyncerLite("", "", registry.Types)
497+
namespace, err := syncer.SourceToNamespace(ctx, f)
498+
if err != nil {
499+
panic(err)
500+
}
501+
//fmt.Printf("%+v\n", namespace)
502+
//fmt.Printf("%+v\n", registry.Types)
503+
//fmt.Print("ON TO GENERATION\n")
504+
// code gen based off that namespace object
505+
g, err := gen.NewGoGenerator("", "/tmp", "", "", namespace.Name) // type registry?
506+
g.TypeRegistry = registry.Types
507+
if err != nil {
508+
panic(err)
509+
}
510+
_, privateFile, err := g.GenNamespaceFiles(ctx, namespace.Features, nil)
511+
if err != nil {
512+
panic(err)
513+
}
514+
return privateFile
515+
}
516+
517+
func writeProtoFiles(fds *descriptorpb.FileDescriptorSet) map[string]string {
518+
ret := make(map[string]string)
519+
for _, fdProto := range fds.File {
520+
protoContent := reconstructProto(fdProto)
521+
ret[fdProto.GetName()] = protoContent
522+
}
523+
return ret
524+
}
525+
526+
func reconstructProto(fdProto *descriptorpb.FileDescriptorProto) string {
527+
var sb strings.Builder
528+
529+
sb.WriteString("syntax = \"proto3\";\n\n")
530+
531+
if pkg := fdProto.GetPackage(); pkg != "" {
532+
sb.WriteString(fmt.Sprintf("package %s;\n\n", pkg))
533+
}
534+
535+
for _, dep := range fdProto.Dependency {
536+
sb.WriteString(fmt.Sprintf("import \"%s\";\n", dep))
537+
}
538+
sb.WriteString("\n")
539+
540+
for _, msg := range fdProto.MessageType {
541+
reconstructMessage(&sb, msg, 0)
542+
}
543+
544+
for _, enum := range fdProto.EnumType {
545+
reconstructEnum(&sb, enum, 0)
546+
}
547+
548+
for _, svc := range fdProto.Service {
549+
reconstructService(&sb, svc, 0)
550+
}
551+
552+
return sb.String()
553+
}
554+
555+
func reconstructMessage(sb *strings.Builder, msg *descriptorpb.DescriptorProto, indentLevel int) {
556+
indent := strings.Repeat(" ", indentLevel)
557+
sb.WriteString(fmt.Sprintf("%smessage %s {\n", indent, msg.GetName()))
558+
559+
// Identify map entry types to avoid nesting them
560+
mapEntries := make(map[string]bool)
561+
for _, field := range msg.Field {
562+
if isMapEntry(field, msg) {
563+
keyType, valueType := getMapTypes(field, msg)
564+
sb.WriteString(fmt.Sprintf("%s map<%s, %s> %s = %d;\n", indent, keyType, valueType, field.GetName(), field.GetNumber()))
565+
mapEntries[getMapEntryName(field)] = true
566+
} else {
567+
fieldType := getFieldType(field)
568+
fieldLabel := getFieldLabel(field)
569+
sb.WriteString(fmt.Sprintf("%s %s %s %s = %d;\n", indent, fieldLabel, fieldType, field.GetName(), field.GetNumber()))
570+
}
571+
}
572+
573+
// Include nested types, excluding map entries
574+
for _, nestedMsg := range msg.NestedType {
575+
if !mapEntries[nestedMsg.GetName()] {
576+
reconstructMessage(sb, nestedMsg, indentLevel+1)
577+
}
578+
}
579+
580+
for _, enum := range msg.EnumType {
581+
reconstructEnum(sb, enum, indentLevel+1)
582+
}
583+
584+
sb.WriteString(fmt.Sprintf("%s}\n\n", indent))
585+
}
586+
587+
func reconstructEnum(sb *strings.Builder, enum *descriptorpb.EnumDescriptorProto, indentLevel int) {
588+
indent := strings.Repeat(" ", indentLevel)
589+
sb.WriteString(fmt.Sprintf("%senum %s {\n", indent, enum.GetName()))
590+
591+
for _, value := range enum.Value {
592+
sb.WriteString(fmt.Sprintf("%s %s = %d;\n", indent, value.GetName(), value.GetNumber()))
593+
}
594+
595+
sb.WriteString(fmt.Sprintf("%s}\n\n", indent))
596+
}
597+
598+
func reconstructService(sb *strings.Builder, svc *descriptorpb.ServiceDescriptorProto, indentLevel int) {
599+
indent := strings.Repeat(" ", indentLevel)
600+
sb.WriteString(fmt.Sprintf("%sservice %s {\n", indent, svc.GetName()))
601+
602+
for _, method := range svc.Method {
603+
sb.WriteString(fmt.Sprintf("%s rpc %s (%s) returns (%s);\n", indent, method.GetName(), trimPackage(method.GetInputType()), trimPackage(method.GetOutputType())))
604+
}
605+
606+
sb.WriteString(fmt.Sprintf("%s}\n\n", indent))
607+
}
608+
609+
func getFieldType(field *descriptorpb.FieldDescriptorProto) string {
610+
switch *field.Type {
611+
case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:
612+
return "double"
613+
case descriptorpb.FieldDescriptorProto_TYPE_FLOAT:
614+
return "float"
615+
case descriptorpb.FieldDescriptorProto_TYPE_INT64:
616+
return "int64"
617+
case descriptorpb.FieldDescriptorProto_TYPE_UINT64:
618+
return "uint64"
619+
case descriptorpb.FieldDescriptorProto_TYPE_INT32:
620+
return "int32"
621+
case descriptorpb.FieldDescriptorProto_TYPE_FIXED64:
622+
return "fixed64"
623+
case descriptorpb.FieldDescriptorProto_TYPE_FIXED32:
624+
return "fixed32"
625+
case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
626+
return "bool"
627+
case descriptorpb.FieldDescriptorProto_TYPE_STRING:
628+
return "string"
629+
case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
630+
return "bytes"
631+
case descriptorpb.FieldDescriptorProto_TYPE_UINT32:
632+
return "uint32"
633+
case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
634+
return trimPackage(field.GetTypeName())
635+
case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32:
636+
return "sfixed32"
637+
case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64:
638+
return "sfixed64"
639+
case descriptorpb.FieldDescriptorProto_TYPE_SINT32:
640+
return "sint32"
641+
case descriptorpb.FieldDescriptorProto_TYPE_SINT64:
642+
return "sint64"
643+
case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
644+
return trimPackage(field.GetTypeName())
645+
case descriptorpb.FieldDescriptorProto_TYPE_GROUP:
646+
return "group"
647+
default:
648+
return "unknown"
649+
}
650+
}
651+
652+
func getFieldLabel(field *descriptorpb.FieldDescriptorProto) string {
653+
if field.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
654+
return "repeated"
655+
}
656+
return ""
657+
}
658+
659+
func isMapEntry(field *descriptorpb.FieldDescriptorProto, msg *descriptorpb.DescriptorProto) bool {
660+
for _, nested := range msg.NestedType {
661+
if nested.GetName() == getMapEntryName(field) && nested.GetOptions().GetMapEntry() {
662+
return true
663+
}
664+
}
665+
return false
666+
}
667+
668+
func getMapEntryName(field *descriptorpb.FieldDescriptorProto) string {
669+
parts := strings.Split(field.GetTypeName(), ".")
670+
return parts[len(parts)-1]
671+
}
672+
673+
func getMapTypes(field *descriptorpb.FieldDescriptorProto, msg *descriptorpb.DescriptorProto) (string, string) {
674+
for _, nested := range msg.NestedType {
675+
if nested.GetName() == getMapEntryName(field) {
676+
var keyType, valueType string
677+
for _, nestedField := range nested.Field {
678+
if nestedField.GetName() == "key" {
679+
keyType = getFieldType(nestedField)
680+
} else if nestedField.GetName() == "value" {
681+
valueType = getFieldType(nestedField)
682+
}
683+
}
684+
return keyType, valueType
685+
}
686+
}
687+
return "unknown", "unknown"
688+
}
689+
690+
func trimPackage(typeName string) string {
691+
if len(typeName) > 0 && typeName[0] == '.' {
692+
return typeName[1:]
693+
}
694+
return typeName
695+
}
696+
697+
func TypesToFileDescriptorSet(types *protoregistry.Types) *descriptorpb.FileDescriptorSet {
698+
fdSet := &descriptorpb.FileDescriptorSet{}
699+
files := &protoregistry.Files{}
700+
701+
types.RangeMessages(func(mt protoreflect.MessageType) bool {
702+
file := mt.Descriptor().ParentFile()
703+
_ = files.RegisterFile(file)
704+
return true
705+
})
706+
707+
files.RangeFiles(func(fileDesc protoreflect.FileDescriptor) bool {
708+
fdProto := protodesc.ToFileDescriptorProto(fileDesc)
709+
fdSet.File = append(fdSet.File, fdProto)
710+
return true
711+
})
712+
713+
return fdSet
714+
}
715+
716+
func WriteToRepo(ctx context.Context, r repo.ConfigurationRepository, types *protoregistry.Types) error {
717+
rootMD, _, err := r.ParseMetadata(ctx)
718+
if err != nil {
719+
return err
720+
}
721+
fds := TypesToFileDescriptorSet(types)
722+
files := writeProtoFiles(fds)
723+
for fn, contents := range files {
724+
if strings.HasSuffix(fn, "/config/v1beta1/lekko.proto") {
725+
path := filepath.Join(rootMD.ProtoDirectory, fn)
726+
err = r.WriteFile(path, []byte(contents), 0600)
727+
if err != nil {
728+
panic(err)
729+
}
730+
}
731+
}
732+
return nil
733+
}

0 commit comments

Comments
 (0)