Skip to content

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

Merged

Conversation

varshaprasad96
Copy link
Contributor

@varshaprasad96 varshaprasad96 commented May 19, 2022

Supersedes #14

Fixes: #10

@varshaprasad96 varshaprasad96 force-pushed the add-missing-clientgen-markers branch 2 times, most recently from 95451bd to 0f782a4 Compare May 23, 2022 23:24
@varshaprasad96 varshaprasad96 marked this pull request as ready for review May 23, 2022 23:24
@varshaprasad96
Copy link
Contributor Author

varshaprasad96 commented May 23, 2022

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:
For apps:v1 and certificates:v1, here is the output: https://github.com/varshaprasad96/kubernetes/tree/test-scaffolding/staging/src/k8s.io/client-go/clusterclient/typed

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)

@fabianvf
Copy link
Contributor

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.
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The generated methods aren't sorted but in the order as specified here which is the order that code-gen follows. And previously the methods were hardcoded in templates instead of being split as done here. Really sorry for the clumsy diff :(

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
Copy link
Contributor

@stevekuznetsov stevekuznetsov May 26, 2022

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?

Copy link
Contributor Author

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.

  1. The parameter does not take any value. That's why genclient:noVerbs, genclient:readonly are registered as separate markers.
  2. 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 why genclient:Method marker is not defined explicitly, but others are.

Copy link
Contributor

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("")?

Copy link
Contributor Author

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:

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.

Copy link
Contributor

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.

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 have modified the comments for these markers to make it a bit more clear.

Copy link
Contributor

@stevekuznetsov stevekuznetsov left a 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)
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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 -

for _, f := range info.Fields {
if f.Name == "Status" {
hasStatusField = true
break
}
}
, in addition to checking if the marker exists.

Copy link
Contributor

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?

Copy link
Contributor Author

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

  1. noStatus marker is present and status subfield is present or not present - we don't scaffold status sub resources.
  2. noStatus marker is not present - if Status 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"
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@varshaprasad96 varshaprasad96 force-pushed the add-missing-clientgen-markers branch from 0f782a4 to d5edbfd Compare May 26, 2022 18:49
@@ -17,11 +17,13 @@ limitations under the License.
package main

import (
goflags "flag"
Copy link
Member

@njhale njhale May 26, 2022

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

Copy link
Contributor Author

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:

"github.com/kcp-dev/code-generator/pkg/flag"
. Did you mean to rename those instead of tagging this as go flags?

@varshaprasad96 varshaprasad96 force-pushed the add-missing-clientgen-markers branch 2 times, most recently from 0c9a4d7 to 6d704e3 Compare May 26, 2022 19:07
Comment on lines 183 to 185
PkgNameUpperFirst string
VersionUpperFirst string
NameLowerFirst string
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

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 {
Copy link
Member

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?

Comment on lines +246 to +259
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
}
}
Copy link
Member

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?

Copy link
Contributor Author

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)))
Copy link
Member

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.

Copy link
Contributor

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 = `
Copy link
Member

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?

Copy link
Contributor Author

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:

  1. Wanted to offload some of the conditional logic to go code, so that templates don't look clumsy.
  2. 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.

Copy link
Contributor Author

@varshaprasad96 varshaprasad96 May 27, 2022

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.

@stevekuznetsov
Copy link
Contributor

This needs a rebase but is otherwise ready to merge, I think

ncdc and others added 2 commits June 6, 2022 10:18
Signed-off-by: Andy Goldstein <[email protected]>
by upstream k8s code-generator.

Signed-off-by: varshaprasad96 <[email protected]>
@varshaprasad96 varshaprasad96 force-pushed the add-missing-clientgen-markers branch from 9bda2d0 to c64c58b Compare June 6, 2022 21:54
@stevekuznetsov stevekuznetsov merged commit b7945e2 into kcp-dev:main Jun 7, 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.

Add support for all client-gen tags
5 participants