@@ -22,6 +22,7 @@ import (
22
22
"os/exec"
23
23
"path"
24
24
"path/filepath"
25
+ "strings"
25
26
26
27
"golang.org/x/mod/modfile"
27
28
@@ -31,6 +32,8 @@ import (
31
32
"github.com/spf13/cobra"
32
33
"google.golang.org/protobuf/encoding/protojson"
33
34
"google.golang.org/protobuf/proto"
35
+ "google.golang.org/protobuf/reflect/protodesc"
36
+ "google.golang.org/protobuf/reflect/protoreflect"
34
37
"google.golang.org/protobuf/reflect/protoregistry"
35
38
"google.golang.org/protobuf/types/descriptorpb"
36
39
"google.golang.org/protobuf/types/known/anypb"
@@ -87,7 +90,11 @@ func syncGoCmd() *cobra.Command {
87
90
if err != nil {
88
91
return err
89
92
}
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 )
91
98
},
92
99
}
93
100
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
451
458
}
452
459
return st .Types , nil
453
460
}
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