-
Notifications
You must be signed in to change notification settings - Fork 15
Add missing clientgen markers #17
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 missing clientgen markers #17
Conversation
95451bd
to
0f782a4
Compare
cc: @ncdc @fabianvf @njhale The PR is ready for review. With this, the additional genclient formats are now supported. Tested this out with some upstream k8s types: Unfortunately the entire k8s api don't work in one go. Digging into the upstream k8s code-gen, realized they have a patch to add plural names for the types which needs to be brought in here. I'll create an issue for that and fix it in follow up. (#19) |
part of kcp-dev/kcp#759 |
@@ -78,76 +77,76 @@ func (w *wrappedClusterTestType) checkCluster(ctx context.Context) (context.Cont | |||
return ctx, nil | |||
} | |||
|
|||
// Create implements ClusterTestTypeInterface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we sort the methods being generated, perhaps adding first a commit to sort the previous generated output, so that this diff is legible, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pkg/generators/clientgen/gen.go
Outdated
noVerbsMarker = markers.Must(markers.MakeDefinition("genclient:noVerbs", markers.DescribesType, struct{}{})) | ||
readOnlyMarker = markers.Must(markers.MakeDefinition("genclient:readonly", markers.DescribesType, struct{}{})) | ||
|
||
// These two markers are defined separately since they accept a list which is comma separated. In controller-tools, comma |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "defined separately" mean? If I'm changing this file in the future, what do I need to make sure I do so nothing breaks here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In controller-tool's terms the following format is how the marker is defined: <makername>:<parameter>=<values>
. As a consumer of this library, I need to register (what MakeDefinition
does here) just the marker name once. But there are 2 edge cases, because of which we are defining the marker formats separately.
- The parameter does not take any value. That's why
genclient:noVerbs
,genclient:readonly
are registered as separate markers. - The value accepts a list. Controller-tool's parsing logic identifies a list only if it is separated by ";", whereas genclinet formats have comma separated values. This confuses the parser, and screws up the logic. To work around this, we define this as separate markers.
Just as an example - this is the reason whygenclient:Method
marker is not defined explicitly, but others are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So by "separately" you mean that we are passsing markers.RawArguments("")
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not exactly, I meant that we are registering it to markers list explicitly using MakeDefinition
(ie) this:
code-generator/pkg/generators/clientgen/gen.go
Lines 52 to 53 in d5edbfd
skipVerbsMarker = markers.Must(markers.MakeDefinition("genclient:skipVerbs", markers.DescribesType, markers.RawArguments(""))) | |
onlyVerbsMarker = markers.Must(markers.MakeDefinition("genclient:onlyVerbs", markers.DescribesType, markers.RawArguments(""))) |
The method
marker (ie) genclient:method
doesn't need that kind explicit registration because it's considered as a sub-type of genclient
marker. markers.RawArguments("")
is actually just parsing whatever value is being passed to the respective marker (say genclient:onlyVerb
) and returning to us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see - comparing everything here (from nonNamespacedMarker
down) to ones that exist but are not even listed. I see. I was trying to tell the difference between e.g. these two under the comment and the ones above them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have modified the comments for these markers to make it a bit more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, it does not seem like we have a clear distinction between data aggregation and output formatting. IMO when using Go templates, we should aggregate data in the language and format it in the template.
@@ -357,16 +380,14 @@ func (g *Generator) generateSubInterfaces(ctx *genall.GenerationContext) error { | |||
// this is to accomodate multiple types defined in single group | |||
byType := make(map[string][]byte) | |||
|
|||
importList := make([]string, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make appending to this an explicit thing instead of passing a pointer and doing an implicit/side-effect mutation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea was to allow the subsequent functions (like scaffolding APIs) to modify the import list which is why it is passed as pointer. We could pass it explicitly and do an aggregation in the end. But wondering if that is required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think passing mutable state around to have side-effects change it makes for code that's hard to reason about, but we can figure it out in a follow-up.
|
||
namespaceScoped := info.Markers.Get(nonNamespacedMarker.Name) == nil | ||
noVerbs := info.Markers.Get(noVerbsMarker.Name) != nil | ||
hasStatus := hasStatusSubresource(info) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we get rid of the other helpers, but not this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's because it does the additional task of checking whether the resource field is present for the type or not -
code-generator/pkg/generators/clientgen/gen.go
Lines 452 to 457 in 4977dcf
for _, f := range info.Fields { | |
if f.Name == "Status" { | |
hasStatusField = true | |
break | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we emit some sort of validation error when a user asks for a status update but their type does not have it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, that condition doesn't arise. Because this is a noStatus
marker. If
noStatus
marker is present and status subfield is present or not present - we don't scaffold status sub resources.noStatus
marker is not present - ifStatus
field is there its scaffolded, if not its not scaffolded.
{{.Name}}{{.Version}} "{{.ClientPath}}/typed/{{.Name}}/{{.Version}}" | ||
|
||
kcp "github.com/kcp-dev/apimachinery/pkg/client" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is part of appendGenericImports
, do I understand correctly that these are always imported? Why move this logic into Go from the template?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, just to have everything added in a same place. Could be either ways.
0f782a4
to
d5edbfd
Compare
@@ -17,11 +17,13 @@ limitations under the License. | |||
package main | |||
|
|||
import ( | |||
goflags "flag" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is probably an indication that we either shouldn't use the name flag
OR the flags should be exported from another package; e.g. pkg/generators
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have flags imported internally here:
Line 28 in 4977dcf
"github.com/kcp-dev/code-generator/pkg/flag" |
go flags
?
0c9a4d7
to
6d704e3
Compare
pkg/internal/parser.go
Outdated
PkgNameUpperFirst string | ||
VersionUpperFirst string | ||
NameLowerFirst string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can expose formatting methods to be used directly in the Go template ala https://github.com/njhale/code-generator/blob/77c28e0b5b75e66b76ec77e7ca1379871c96bdeb/pkg/internal/informergen/parser.go#L73 and https://github.com/njhale/code-generator/blob/77c28e0b5b75e66b76ec77e7ca1379871c96bdeb/pkg/internal/informergen/templates.go#L39
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's what I was planning to do here in a follow-up: bf72d7f#diff-7cd18745c2d89da8fe056843208f0d785327606243491e7044af3bd0e4dedaf1R387-R395. Removing all the formatting from structs would clear up the code.
util.ImportFormat(fmt.Sprintf("%s%s", p.Name, p.Version), fmt.Sprintf("%s/typed/%s/%s", p.ClientPath, p.Name, p.Version))) | ||
} | ||
|
||
func adjustTemplate(template, verb string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the conditionals can be in the template itself, no?
if a.hasVerb("get") { | ||
if err := templateExecute(getTemplate, *a); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if a.hasVerb("list") { | ||
if err := templateExecute(listTemplate, *a); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if a.hasVerb("watch") { | ||
if err := templateExecute(watchTemplate, *a); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if a.hasVerb("create") { | ||
if err := templateExecute(createTemplate, *a); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if a.hasVerb("update") { | ||
if err := templateExecute(updateTemplate, *a); err != nil { | ||
return err | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't the conditionals for this live in the template itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explained here: #17 (comment)
} | ||
|
||
if a.hasVerb("apply") && generateApply { | ||
*a.importList = append(*a.importList, util.ImportFormat(fmt.Sprintf("%sapply%s", a.PkgName, a.Version), fmt.Sprintf("%s/%s/%s", a.ApplyConfigurationPackage, a.PkgName, a.Version))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like we need this to be a slice pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to mutate it in these callers, you need the pointer.
@@ -166,45 +157,86 @@ func (w *wrapped{{.Name}}) checkCluster(ctx context.Context) (context.Context, e | |||
} | |||
return ctx, nil | |||
} | |||
` | |||
const patchTemplate = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can join all these templates and add conditionals around them, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reasons why I split the templates was:
- Wanted to offload some of the conditional logic to go code, so that templates don't look clumsy.
- Most importantly, the additionalMethods (
extensions
as called by upstream code-gen) need modification of specific verbs by overriding their input and result type. Merging all of this into one will make it really difficult to implement those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(2) was the most important reason to split the templates. That's the reason I am still inclined towards having them separate instead of making everything into one single block even though the side-effect is to have a few more if
statements when individual verbs are to be scaffolded.
6d704e3
to
9bda2d0
Compare
This needs a rebase but is otherwise ready to merge, I think |
Signed-off-by: Andy Goldstein <[email protected]>
by upstream k8s code-generator. Signed-off-by: varshaprasad96 <[email protected]>
9bda2d0
to
c64c58b
Compare
Supersedes #14
Fixes: #10