Skip to content

Commit

Permalink
Add protoc-gen-auth plugin to generate the service.pb.auth.go automat…
Browse files Browse the repository at this point in the history
…ically
  • Loading branch information
knanao committed May 12, 2022
1 parent 81fd933 commit c403aee
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 0 deletions.
3 changes: 3 additions & 0 deletions tool/codegen/codegen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ for dir in ${goProtoDirs[*]}; do
protoc \
-I . \
-I /go/src/github.com/envoyproxy/protoc-gen-validate \
--plugin=./tool/codegen/protoc-gen-auth/protoc-gen-auth \
--go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
--validate_out="lang=go:." \
--validate_opt=paths=source_relative \
--auth_out=. \
--auth_opt=paths=source_relative \
${dir}/*.proto
echo "successfully generated"
done
Expand Down
3 changes: 3 additions & 0 deletions tool/codegen/protoc-gen-auth/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.PHONY: build
build:
GOOS=linux GOARCH=amd64 go build -o protoc-gen-auth .
62 changes: 62 additions & 0 deletions tool/codegen/protoc-gen-auth/file.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions tool/codegen/protoc-gen-auth/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/pipe-cd/pipecd/tool/codegen/protoc-gen-auth

go 1.17

require google.golang.org/protobuf v1.28.0
8 changes: 8 additions & 0 deletions tool/codegen/protoc-gen-auth/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
146 changes: 146 additions & 0 deletions tool/codegen/protoc-gen-auth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package main

import (
"bytes"
"fmt"
"html/template"
"sort"
"strings"

"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/dynamicpb"
)

type FileParams struct {
InputPath string
Methods []*Method
}

type Method struct {
Name string // The name of the RPC
Role string // ADMIN,EDITOR,VIEWER
}

const (
filePrefix = "pkg/app/server/service/webservice"
generatedFileNameSuffix = ".pb.auth.go"
protoFileExtention = ".proto"
methodOptionsRole = "role"
)

func main() {
protogen.Options{}.Run(func(p *protogen.Plugin) error {
extTypes := new(protoregistry.Types)
for _, f := range p.Files {
if err := registerAllExtensions(extTypes, f.Desc); err != nil {
return fmt.Errorf("registerAllExtensions error: %v", err)
}

if !f.Generate || !strings.Contains(f.GeneratedFilenamePrefix, filePrefix) {
continue
}

methods := make([]*Method, 0, len(f.Services)*len(f.Services[0].Methods))
for _, svc := range f.Services {
ms, err := generateMethods(extTypes, svc.Methods)
if err != nil {
return fmt.Errorf("generateMethods error: %v", err)
}
methods = append(methods, ms...)
}

filename := fmt.Sprintf("%s%s", f.GeneratedFilenamePrefix, generatedFileNameSuffix)
gf := p.NewGeneratedFile(filename, f.GoImportPath)

sort.SliceStable(methods, func(i, j int) bool {
return methods[i].Role < methods[j].Role
})

inputPath := fmt.Sprintf("%s%s", f.GeneratedFilenamePrefix, protoFileExtention)
fp := &FileParams{
InputPath: inputPath,
Methods: methods,
}

buf := bytes.Buffer{}
t := template.Must(template.New("auth").Parse(fileTpl))
if err := t.Execute(&buf, fp); err != nil {
return fmt.Errorf("template execute error: %v", err)
}
gf.P(string(buf.Bytes()))
}
return nil
})
}

// generateMethods generates the []*Method from []*protogen.Method for pasing template.
// The MessageOptions as provided by protoc does not know about dynamically created extensions,
// so they are left as unknown fields. We round-trip marshal and unmarshal the options
// with a dynamically created resolver that does know about extensions at runtime.
// https://github.com/golang/protobuf/issues/1260#issuecomment-751517894
func generateMethods(extTypes *protoregistry.Types, ms []*protogen.Method) ([]*Method, error) {
ret := make([]*Method, 0, len(ms))
for _, m := range ms {
opts := m.Desc.Options().(*descriptorpb.MethodOptions)
raw, err := proto.Marshal(opts)
if err != nil {
return nil, err
}

opts.Reset()
err = proto.UnmarshalOptions{Resolver: extTypes}.Unmarshal(raw, opts)
if err != nil {
return nil, err
}

method := &Method{Name: m.GoName}
opts.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if !fd.IsExtension() {
return true
}

if fd.Name() == methodOptionsRole {
// FIXME: This way can not parse the first value of enum for some reasons hence
// set VIEWER for default value.
method.Role = "VIEWER"
if v.String() != "" {
method.Role = strings.SplitN(v.String(), ":", 2)[1]
}
}
return true
})

if method.Role != "" {
ret = append(ret, method)
}
}
return ret, nil
}

// registerAllExtensions recursively registers all extensions into the provided protoregistry.Types,
// starting with the protoreflect.FileDescriptor and recursing into its MessageDescriptors,
// their nested MessageDescriptors, and so on.
//
// This leverages the fact that both protoreflect.FileDescriptor and protoreflect.MessageDescriptor
// have identical Messages() and Extensions() functions in order to recurse through a single function.
// https://github.com/golang/protobuf/issues/1260#issuecomment-751517894
func registerAllExtensions(extTypes *protoregistry.Types, descs interface {
Messages() protoreflect.MessageDescriptors
Extensions() protoreflect.ExtensionDescriptors
}) error {
mds := descs.Messages()
for i := 0; i < mds.Len(); i++ {
registerAllExtensions(extTypes, mds.Get(i))
}
xds := descs.Extensions()
for i := 0; i < xds.Len(); i++ {
if err := extTypes.RegisterExtension(dynamicpb.NewExtensionType(xds.Get(i))); err != nil {
return err
}
}
return nil
}
Binary file added tool/codegen/protoc-gen-auth/protoc-gen-auth
Binary file not shown.

0 comments on commit c403aee

Please sign in to comment.