-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
codegen: enable CRD schema generation using protoc-gen-openapi (#539)
* codegen: enable CRD schema generation using protoc-gen-openapi Allows generating CRDs schema using protoc-gen-openapi instead of Cue. This is a part of a larger effort to customize schema generation that is harder to accomplish using Cue: solo-io/gloo-mesh-enterprise#16049 Signed-off-by: Shashank Ram <[email protected]> * update deps and codegen * add test --------- Signed-off-by: Shashank Ram <[email protected]>
- Loading branch information
1 parent
aaa4fae
commit b550b99
Showing
14 changed files
with
1,188 additions
and
367 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ export PATH:=$(GOBIN):$(PATH) | |
install-go-tools: mod-download | ||
mkdir -p $(DEPSGOBIN) | ||
go install github.com/golang/protobuf/[email protected] | ||
go install github.com/solo-io/[email protected] | ||
go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc | ||
go install github.com/solo-io/[email protected] | ||
go install github.com/golang/mock/[email protected] | ||
|
@@ -57,7 +58,7 @@ generated-code: install-tools update-licenses | |
go run api/generate.go | ||
# the api/generate.go command is separated out to enable us to run go generate on the generated files (used for mockgen) | ||
# this re-gens test protos | ||
go test ./codegen | ||
PATH=$(DEPSGOBIN):$(PATH) go test ./codegen | ||
go generate -v ./... | ||
$(DEPSGOBIN)/goimports -w . | ||
go mod tidy | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
changelog: | ||
- type: BREAKING_CHANGE | ||
issueLink: https://github.com/solo-io/gloo-mesh-enterprise/issues/16049 | ||
resolvesIssue: false | ||
description: > | ||
"Enable CRD schema generation using protoc-gen-openapi" | ||
skipCI: "false" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package collector | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/rotisserie/eris" | ||
) | ||
|
||
type ProtocExecutor interface { | ||
Execute(protoFile string, toFile string, imports []string) error | ||
} | ||
|
||
type DefaultProtocExecutor struct { | ||
// The output directory | ||
OutputDir string | ||
// whether or not to do a regular go-proto generate while collecting descriptors | ||
ShouldCompileFile func(string) bool | ||
// arguments for go_out= | ||
CustomGoArgs []string | ||
// custom plugins | ||
// each will append a <plugin>_out= directive to protoc command | ||
CustomPlugins []string | ||
} | ||
|
||
var defaultGoArgs = []string{ | ||
"plugins=grpc", | ||
} | ||
|
||
func (d *DefaultProtocExecutor) Execute(protoFile string, toFile string, imports []string) error { | ||
cmd := exec.Command("protoc") | ||
|
||
for _, i := range imports { | ||
cmd.Args = append(cmd.Args, fmt.Sprintf("-I%s", i)) | ||
} | ||
|
||
if d.ShouldCompileFile(protoFile) { | ||
goArgs := append(defaultGoArgs, d.CustomGoArgs...) | ||
goArgsJoined := strings.Join(goArgs, ",") | ||
cmd.Args = append(cmd.Args, | ||
fmt.Sprintf("--go_out=%s:%s", goArgsJoined, d.OutputDir), | ||
fmt.Sprintf("--ext_out=%s:%s", goArgsJoined, d.OutputDir), | ||
) | ||
|
||
for _, pluginName := range d.CustomPlugins { | ||
cmd.Args = append(cmd.Args, | ||
fmt.Sprintf("--%s_out=%s:%s", pluginName, goArgsJoined, d.OutputDir), | ||
) | ||
} | ||
} | ||
|
||
cmd.Args = append(cmd.Args, | ||
"-o", | ||
toFile, | ||
"--include_imports", | ||
"--include_source_info", | ||
protoFile) | ||
|
||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return eris.Wrapf(err, "%v failed: %s", cmd.Args, out) | ||
} | ||
return nil | ||
} | ||
|
||
type OpenApiProtocExecutor struct { | ||
OutputDir string | ||
|
||
// Whether to include descriptions in validation schemas | ||
IncludeDescriptionsInSchema bool | ||
|
||
// Whether to assign Enum fields the `x-kubernetes-int-or-string` property | ||
// which allows the value to either be an integer or a string | ||
EnumAsIntOrString bool | ||
|
||
// A list of messages (core.solo.io.Status) whose validation schema should | ||
// not be generated | ||
MessagesWithEmptySchema []string | ||
} | ||
|
||
func (o *OpenApiProtocExecutor) Execute(protoFile string, toFile string, imports []string) error { | ||
cmd := exec.Command("protoc") | ||
|
||
for _, i := range imports { | ||
cmd.Args = append(cmd.Args, fmt.Sprintf("-I%s", i)) | ||
} | ||
|
||
// The way that --openapi_out works, is that it produces a file in an output directory, | ||
// with the name of the file matching the proto package (ie gloo.solo.io). | ||
// Therefore, if you have multiple protos in a single package, they will all be output | ||
// to the same file, and overwrite one another. | ||
// To avoid this, we generate a directory with the name of the proto file. | ||
// For example my_resource.proto in the gloo.solo.io package will produce the following file: | ||
// my_resource/gloo.solo.io.yaml | ||
|
||
// The directoryName is created by taking the name of the file and removing the extension | ||
_, fileName := filepath.Split(protoFile) | ||
directoryName := fileName[0 : len(fileName)-len(filepath.Ext(fileName))] | ||
|
||
// Create the directory | ||
directoryPath := filepath.Join(o.OutputDir, directoryName) | ||
_ = os.Mkdir(directoryPath, os.ModePerm) | ||
|
||
cmd.Args = append(cmd.Args, | ||
fmt.Sprintf("--openapi_out=yaml=true,single_file=false,include_description=true,multiline_description=true,enum_as_int_or_string=%v,proto_oneof=true,int_native=true,additional_empty_schema=%v:%s", | ||
o.EnumAsIntOrString, | ||
strings.Join(o.MessagesWithEmptySchema, "+"), | ||
directoryPath), | ||
) | ||
|
||
cmd.Args = append(cmd.Args, | ||
"-o", | ||
toFile, | ||
"--include_imports", | ||
"--include_source_info", | ||
protoFile) | ||
|
||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return eris.Wrapf(err, "%v failed: %s", cmd.Args, out) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package schemagen | ||
|
||
type GeneratorKind string | ||
|
||
const ( | ||
Cue GeneratorKind = "cue" | ||
ProtocGenOpenAPI GeneratorKind = "protoc-gen-openapi" | ||
) | ||
|
||
type ValidationSchemaOptions struct { | ||
// Whether to assign Enum fields the `x-kubernetes-int-or-string` property | ||
// which allows the value to either be an integer or a string | ||
// If this is false, only string values are allowed | ||
// Default: false | ||
EnumAsIntOrString bool | ||
|
||
// A list of messages (e.g. ratelimit.api.solo.io.Descriptor) whose validation schema should | ||
// not be generated | ||
MessagesWithEmptySchema []string | ||
} | ||
|
||
// prevent k8s from validating proto.Any fields (since it's unstructured) | ||
func removeProtoAnyValidation(d map[string]interface{}, propertyField string) { | ||
for _, v := range d { | ||
values, ok := v.(map[string]interface{}) | ||
if !ok { | ||
continue | ||
} | ||
desc, ok := values["properties"] | ||
properties, isObj := desc.(map[string]interface{}) | ||
// detect proto.Any field from presence of [propertyField] as field under "properties" | ||
if !ok || !isObj || properties[propertyField] == nil { | ||
removeProtoAnyValidation(values, propertyField) | ||
continue | ||
} | ||
// remove "properties" value | ||
delete(values, "properties") | ||
// remove "required" value | ||
delete(values, "required") | ||
// x-kubernetes-preserve-unknown-fields allows for unknown fields from a particular node | ||
// see https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema | ||
values["x-kubernetes-preserve-unknown-fields"] = true | ||
} | ||
} |
Oops, something went wrong.