Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add protoc-gen-auth plugin to generate the service.pb.auth.go automatically #3623

Merged
merged 3 commits into from May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion tool/codegen/DOCKER_BUILD
@@ -1,2 +1,2 @@
version: 0.7.0
version: 0.8.0
registry: gcr.io/pipecd/codegen
12 changes: 12 additions & 0 deletions tool/codegen/Dockerfile
@@ -1,3 +1,12 @@
# Builder image to build go program.
FROM golang:1.17-alpine3.15 as BUILDER

COPY protoc-gen-auth /protoc-gen-auth
RUN cd /protoc-gen-auth \
&& go build -o /usr/local/bin/protoc-gen-auth . \
&& chmod +x /usr/local/bin/protoc-gen-auth

# Codegen image which is actually being used.
FROM golang:1.17-alpine3.15

ENV PROTOC_VER=3.19.4
Expand Down Expand Up @@ -38,6 +47,9 @@ RUN go install github.com/envoyproxy/protoc-gen-validate@v${PROTOC_GEN_VALIDATE_
&& rm protoc-gen-validate.tar.gz \
&& mv /go/src/github.com/envoyproxy/protoc-gen-validate-${PROTOC_GEN_VALIDATE_VER} /go/src/github.com/envoyproxy/protoc-gen-validate

# protoc-gen-auth
COPY --from=BUILDER /usr/local/bin/protoc-gen-auth /usr/local/bin/

knanao marked this conversation as resolved.
Show resolved Hide resolved
# gomock
RUN go install github.com/golang/mock/mockgen@v${GOMOCK_VER}

Expand Down
2 changes: 2 additions & 0 deletions tool/codegen/codegen.sh
Expand Up @@ -38,6 +38,8 @@ for dir in ${goProtoDirs[*]}; do
--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
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
@@ -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
@@ -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
@@ -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
}