Skip to content

Conversation

rcernich
Copy link
Contributor

Signed-off-by: rcernich rcernich@redhat.com

@rcernich rcernich changed the title istio/istio#8772 create tool for generating k8s types wrapping istio types WIP: istio/istio#8772 create tool for generating k8s types wrapping istio types Jan 21, 2019
@istio-testing
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
To fully approve this pull request, please assign additional approvers.
We suggest the following additional approver: geeknoid

If they are not already assigned, you can assign the PR to them by writing /assign @geeknoid in a comment when ready.

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@rcernich rcernich force-pushed the istio-8772 branch 2 times, most recently from 6a639a5 to 8bb7ad5 Compare January 21, 2019 20:21
@rcernich rcernich changed the title WIP: istio/istio#8772 create tool for generating k8s types wrapping istio types istio/istio#8772 create tool for generating k8s types wrapping istio types Jan 21, 2019
@geeknoid
Copy link
Contributor

Sorry for the dumb questions, but I'm a bit confused by what this PR is for exactly.

What is the exact input of the tool? A proto package or a Go package?

And what's the output, and what is that used for?

@rcernich
Copy link
Contributor Author

Hey @geeknoid, I provided a README with the PR. The tool is used to generate Kubernetes types representing the Custom Resource Definitions used by Istio. This tool simply generates a wrapper around this Istio type, e.g.

type Policy struct {
	v1.TypeMeta `json:",inline"`
	// +optional
	v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Spec defines the implementation of this definition.
	// +optional
	Spec v1alpha1.Policy `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
}

Where v1alpha1.Policy is the native Istio type.

The generated code can then be run through other Kubernetes code generators to produce Kubernetes clients, informers, listers, etc., making it easier for folks to work with the Istio resources within Kubernetes.

Here are examples of some of the generated files:

authentication/v1alpha1/types.go
authentication/v1alpha1/register.go

HTH

@geeknoid
Copy link
Contributor

Thanks, I saw the README, but it just mentions an "input package", which given the context could mean a Go package or a proto package.

The work I was expecting in this space was a protoc plugin that takes as input an Istio .proto file and produces as output an OpenAPI definition representing the proto. From this OpenAPI definition, we could then generate all the downstream k8s artifacts.

How would you contrast your approach vs. what I describe? Is your approach preferable overall?

@rcernich
Copy link
Contributor Author

rcernich commented Jan 24, 2019

Sorry... it works off a go package and the go files emitted by protoc. Any types which need to have k8s counterparts are annotated (comment tags) appropriately. Those comments should also include tags to drive the other generators (e.g. client-gen, openapi-gen, etc.)

I can't say what the benefits are of this approach over the protoc plugin approach. I was only focused on getting k8s style apis generated and was not looking at OpenAPI definitions for the raw types. That said, there is a k8s code generator for openapi as well (//+k8s:openapi-gen). If you like, I can update istio/api#764 to include generation of openapi specs.

The k8s code generation framework was easier for me to wrap my head around, as I've never used protoc. Both approaches use the .proto files as the source (comment tags are copied over to the generated go files, then used by the other code generators). One difference was using the package comments in doc.go to identify which packages are scanned and for specifying the target k8s group/version for the types in the package. To me, this seemed less error prone than specifying group/version within each type/message, but does assume that all types in a package will belong to a single k8s group/version. Also, using comment tags to drive code generation seemed like a k8s standard, and thus seemed like it fit in with what folks might already be familiar with (assuming the target audience was k8s devs).

HTH

@rcernich
Copy link
Contributor Author

Here are the diffs for containing the annotations used to drive all the code generation for k8s, istio/api@c2154e2. Focus on the changes to the .proto files and the new doc.go files. The .pb.go files are just duplicates coming out of protoc.

@geeknoid
Copy link
Contributor

Couple more questions:

  • Is doc.go generated or hand-edited? If it's generated, what is doing that work? If its hand-edited, then why is there corresponding comments also in the .proto file?

  • We use comments on the protos to generate docs for istio.io. As is, I think all these comments will make it into the generated .pb.html files and so will show up on istio.io. We need to prevent that. Check out this https://github.com/istio/tools/blob/master/protoc-gen-docs/README.md for info on how we process proto comments.

It would be good to turn on OpenAPI generation on istio/api. We need those files for other things downstream.

Now that I understand better, I'll try and get to the full review later today.

Thanks.

@ozevren
Copy link
Contributor

ozevren commented Jan 24, 2019 via email

@rcernich
Copy link
Contributor Author

doc.go is hand edited. The reason for the duplicates in the type/message comments is that not all types are mapped to k8s api.

I can modify the tool such that generator tags are above the type/message doc section. The k8s code generation tools scan comments above the type comments. That said, I'll need to verify that those comments are copied over by protoc. Another option may be to modify the doc plugin to filter comment lines with go-style tags.

There should not be any issues with serialization, but there was some manual work required to get DeepCopy() working. The workaround I came up with was to provide custom DeepCopyInto() functions for the types used in the k8s apis. The code is rather simple, although probably sub-optimal:

https://github.com/rcernich/istio-api/blob/istio-8772/mixer/v1/config/client/zz_custom.deepcopy.go
https://github.com/rcernich/istio-api/blob/istio-8772/util/deepcopy.go

I did attempt to constrain this to individual fields, but that seemed a bit more difficult. That said, I can revisit that approach to constrain marshal/unmarshal to specific fields. (I bailed as it was simpler to just run the whole type through. This is certainly something that can be optimized.)

I also had to rework the proto imports to make them compatible with the k8s go-to-protobuf generator. This required adding generated.proto files, which are used by go-to-protobuf for locating message definitions. This allows go-to-protobuf to delegate to the existing .pb.go code for the wrapped types. (Which reminds me, I need to filter those files from the make targets.)

It sounds like I need to update the readme in the api project to account for all these details. Summarizing:

To generate k8s api types from istio types:

  1. Add a doc.go file to the containing package, specifying tags for group/version and base target package name.
  2. Add a generated.proto file to the containing package. This file should contain import public "<type.proto>"; lines, importing the message definitions for the types being generated.
  3. Add generator tags to the comments of the message types that should be mapped.
  4. If the type contains oneof fields, a custom DeepCopyInto() function should be provided. (Note, this is easy to detect as there will be compilation errors if it is missing.)

It seems like this may be a lot of manual effort, but other than the DeepCopy() and generated.proto bits, I think any other solution would require a similar amount of effort (i.e. you'll need to identify which types should be mapped and where they should go), and from this point forward, any effort would be incremental.

@rcernich
Copy link
Contributor Author

One additional note, there may be some issues with types that are mapped to multiple k8s types, e.g. namespaced vs clustered resources. These would require two sets of k8s tags to drive downstream code generation (e.g. +genclient:nonNamespaced vs nothing, where MeshPolicy would want the former and Policy the latter, but both are generated from the istio Policy type).

@geeknoid
Copy link
Contributor

Thanks for all the clarifying answers. I've got one more.

I still don't understand why some of the special comments are in doc.go and some are in the .proto file. Wouldn't it be possible to put everything in the .proto file? The protoc compiler carries all the comments forward into the generated go files. I prefer this approach as it makes the .proto the source of truth for our API definitions, as opposed to being only a partial source of truth.

As for hiding the comments from the generated docs, please see if you can make that work with the tools as-is. Otherwise, I can modify the doc gen tool to support skipping blocks within comments.

Finally, it'd be great to have some tests checked in alongside this tool. Something that exercises the core features, including serialization.

Thanks.

@rcernich
Copy link
Contributor Author

doc.go provides a single place to define the target group/version and package for the generated files. I could move that specification into the types, but thought having a single place for this would reduce errors. The main difference would be that +kubetype-gen:package and +kubetype-gen:groupVersion would be specified for every type (which is why I chose to go this way). That said, the alternate is also more flexible, as it would allow types in a package to be split across different groups/versions, but I'm guessing this wouldn't be very common. Perhaps, split the difference and use the package settings as defaults if they're not specified on individual types. I'm indifferent.

Testing...sure, I'll look at adding some tests.

@geeknoid
Copy link
Contributor

I don't think we need (or maybe even want) the flexibility to split the API like you describe. I'd rather force people to split up the package in the first place.

If you move the content of doc.go as an "unattached" comment in a proto file, then I think the protoc compiler will just carry everything as-is into the go file, also unattached to anything. So you end up with semantically the same thing you have now, except that the source of truth is the proto.

@rcernich
Copy link
Contributor Author

I can do that. It will mean the +kubetype-gen:groupVersion and +kubetype-gen:package tags will be duplicated for every type/message in the package. This was the reason I put them in doc.go, to prevent duplication.

@geeknoid geeknoid changed the title istio/istio#8772 create tool for generating k8s types wrapping istio types Tool for generating k8s types wrapping Istio types Jan 25, 2019
@rcernich
Copy link
Contributor Author

Updated with tests.

I removed the package flag and instead use the base of the group (i.e. either group or group of group.some.name).

I added support for adding tags for specific generated types (e.g. genclient:nonNamespaced for cluster scoped resources using the same type as namespaced resources like Policy and MeshPolicy).

I still have to update the PR for the api project using the new generator. This will move the generator comments to the comment section above the type comments and will include the proper tags for generating the clientsets for cluster resources. We'll see how that goes.

@geeknoid
Copy link
Contributor

I'm going through the code now.

@geeknoid geeknoid closed this Jan 28, 2019
@geeknoid geeknoid reopened this Jan 28, 2019
@geeknoid
Copy link
Contributor

geeknoid commented Jan 28, 2019

Sorry about that, I clicked the wrong button

@rcernich
Copy link
Contributor Author

Currently, protoc isn't copying over the detached comments for the types, which means the generator tags need to be in the message comments. I'll keep digging to see if there's a workaround. (I just got builds working in api, so I haven't done much investigating up to this point.)

@rcernich
Copy link
Contributor Author

It looks like protobuf has changed pretty significantly in the last few months and there are many diffs in the generated .pb.go files. The protoc image is using master for golang/protobuf and gogo/protobuf and I'm wondering if these should be using a tag or ref when building the image. Opinions?

@ozevren
Copy link
Contributor

ozevren commented Jan 29, 2019 via email

@rcernich
Copy link
Contributor Author

It's actually this that's the problem. I was able to narrow down to specific commit ids that seem to correlate with the last build of the image.

Copy link
Contributor

@geeknoid geeknoid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good in general. The only issue is the copyright injection in the output.

Thanks.

arguments := args.Default()

arguments.GeneratedByCommentTemplate = "// Code generated by kubetype-gen. DO NOT EDIT."
arguments.GoHeaderFilePath = filepath.Join(args.DefaultSourceTree(), "istio.io/tools/cmd/kubetype-gen/boilerplate.go.txt")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe it's correct to put the Istio copyright on the output of the tool. This would be like putting a copyright from a compiler vendor into produced binaries. The copyright to the material is actually with the author of the original content. I suggest just going with something like:

// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: authentication/v1alpha1/policy.proto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that. I was just following the convention of the other code generators. I just about have the api PR polished up, which should provide a good example of the output of the tool.

Thanks for taking the time to review!

Signed-off-by: rcernich <rcernich@redhat.com>
Signed-off-by: rcernich <rcernich@redhat.com>
@rcernich
Copy link
Contributor Author

rcernich commented Feb 1, 2019

This should be good to go now. Please refer to the individual commits in istio/api#764 to see it in action.

Changes to isio/api source: istio/api@1e4498c

Generated k8s code: istio/api@152cda4

I still looking at cleaning up the Makefile in istio/api. That said, this change needs to be committed first, so I can get the proper info in the Gopkg.lock file.

@geeknoid
Copy link
Contributor

geeknoid commented Feb 1, 2019

Thanks. Looks good.

@geeknoid geeknoid merged commit 3e3c77a into istio:master Feb 1, 2019
Shuanglu pushed a commit to Shuanglu/istio-tools that referenced this pull request Jun 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants