-
Notifications
You must be signed in to change notification settings - Fork 263
feat: Support custom type recursive reference #714
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
base: main
Are you sure you want to change the base?
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: AlessioDiama The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
Welcome @AlessioDiama! |
|
Hi @AlessioDiama. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
jakobmoellerdev
left a comment
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.
Thanks for the PR, this is a super cool feature 🎉 . Im certain we can make this work for KRO but I do believe it needs a bit of polish before a confident merge. PTAL at my comments and let me know what you think!
| @@ -0,0 +1,51 @@ | |||
| --- | |||
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 believe this warrants an E2E / Chainsaw test
pkg/simpleschema/transform.go
Outdated
| // Add dependencies to the DAG and check for cycles | ||
| err := dagInstance.AddDependencies(k, dependencies) | ||
| if err != nil { | ||
| if cycleErr, isCycle := err.(*dag.CycleError[string]); isCycle { |
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.
couldnt we change this to errors.Is
pkg/simpleschema/transform.go
Outdated
| orderedVertexes, err := dagInstance.TopologicalSort() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to build pre-defined types schema: %w", err) | ||
| return fmt.Errorf("failed to sort DAG: %w", 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.
it will be unaparrent to users which DAG youre talking about 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.
not sure what output as a user-understandable error here. Something like:
return fmt.Errorf("failed to get topological order for the types: %w", err)
WDYT?
pkg/simpleschema/transform.go
Outdated
| for _, vertex := range orderedVertexes { | ||
| objValueAtKey, ok := obj[vertex] | ||
| if !ok { | ||
| return fmt.Errorf("failed to assert type for vertex %s", vertex) |
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.
this is not the correct message and also it can never happen. you never look for a key that you didnt add, so this is a noop.
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.
thanks, removed.
pkg/simpleschema/transform.go
Outdated
| objMap := map[string]interface{}{ | ||
| vertex: objValueAtKey, | ||
| } | ||
| if schemaProps, err := t.buildOpenAPISchema(objMap); err == nil { |
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 dont you simply do a normal early return? no need for an if / else check because on error you return from the function. I think this was the behavior befor as well
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.
thanks for the comment. Implemented as suggested
pkg/simpleschema/transform.go
Outdated
| // Remove duplicates using a set | ||
| dependencySet := make(map[string]struct{}) | ||
| for _, dep := range dependencies { | ||
| dependencySet[dep] = struct{}{} |
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 you do this after going through parseMap? wouldnt it be perfectly fine to collect that from the start instead of starting with an array?
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.
As we already have a dependency on k8s apimachinery it could be interesting to re-use the existing Set[comparable] that could help code readability
depedencies := sets.Set[string]{}
[... in the loop]
dependencies.Insert(depName)
[...]
return dependencies.UnsortedList()
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.
thanks for the hint! Changed to use sets.Set[string]{} as suggested
pkg/simpleschema/transform.go
Outdated
| parseMap = func(m map[string]interface{}) { | ||
| for _, value := range m { | ||
| switch v := value.(type) { | ||
| case 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.
Because you now have semantic meaning attached to types, I believe the types that are allowed need much stricter naming restrictions to avoid issues in the future. Things I am thinking about:
- Disallow Special Characters (for example "|" would be a problem, or starting with "[" and "]")
- Optionally enforce UpperCamelCase
- Maybe some other things that im not aware of that could actually break dependency extraction What happens with "[][]Type" for example
In general, maybe think what can break the parser you built here and try to make it a bit more robust. I think those restrictions should be added onto the CRD itself as part of input validation, and not here in the parser.
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 could reuse some of the functions already defined in https://github.com/kubernetes-sigs/kro/blob/main/pkg/simpleschema/atomic.go#L49-L138
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've pushed a new implementation for the extractDependenciesFromMap func. Now i am using the functions from https://github.com/kubernetes-sigs/kro/blob/main/pkg/simpleschema/atomic.go#L49-L138 to make sure to type parsing is more effective.
I believe it is still not 100% safe regarding to all the cases described by @jakobmoellerdev. I would need as well some indications on how to enforce restrictions "onto the CRD itself as part of input validation" as suggested above
|
/ok-to-test |
|
I think its time we split off the simple schema into its own top level package with tests. In KRO we should just include new versions of the simple schema. We should be able to be faster evolving simple-schema if we decouple KRO usage from the schema. |
|
I understand that thought but would like separate that out into its own issue. Dont think we have to discuss it here on this PR, as this should be tackled separately. |
cmd/controller/__debug_bin4030145734
Outdated
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.
Accidental check in, I assume?
| //Constructs a dag of the dependencies between the types | ||
| //If there is a cycle in the graph, then there is a cyclic dependency between the types | ||
| //and we cannot load the types |
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:
| //Constructs a dag of the dependencies between the types | |
| //If there is a cycle in the graph, then there is a cyclic dependency between the types | |
| //and we cannot load the types | |
| // Constructs a dag of the dependencies between the types | |
| // If there is a cycle in the graph, then there is a cyclic dependency between the types | |
| // and we cannot load the types |
pkg/simpleschema/transform.go
Outdated
| var parseMap func(map[string]interface{}) | ||
| parseMap = func(m map[string]interface{}) { |
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.
This anonymous function calling itself recursively, with the dependencies closure, is kind of wild! :)
I think we should make this a named unexported function.
pkg/simpleschema/transform.go
Outdated
| result := make([]string, 0, len(dependencySet)) | ||
| for dep := range dependencySet { | ||
| result = append(result, dep) | ||
| } |
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:
| result := make([]string, 0, len(dependencySet)) | |
| for dep := range dependencySet { | |
| result = append(result, dep) | |
| } | |
| result := slices.Collect(maps.Keys(dependencySet)) |
Could also use standard lib functionality here.
pkg/simpleschema/transform.go
Outdated
| // Define the set of basic types as | ||
| // defined in https://kro.run/docs/concepts/simple-schema#basic-types | ||
| var excludedTypes = map[string]struct{}{ | ||
| "string": {}, | ||
| "integer": {}, | ||
| "bool": {}, | ||
| "float": {}, | ||
| "object": {}, | ||
| } |
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 like this idea, let's address this in a seperate PR?
| schema: | ||
| types: | ||
| Person: | ||
| name: string | ||
| role: "Role" | ||
| Division: | ||
| name: string | ||
| employees: "[]Person" | ||
| Role: | ||
| name: string | ||
| seniority: 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.
Will types get inside the CompanyConfig CRD? or is it a construct that stays private within the RGD?
From birds view, it would be interesting to keep it as a construct and exclude it from the CompanyConfig CRD. I would hence advocate for moving it under spec
| schema: | |
| types: | |
| Person: | |
| name: string | |
| role: "Role" | |
| Division: | |
| name: string | |
| employees: "[]Person" | |
| Role: | |
| name: string | |
| seniority: string | |
| types: | |
| Person: | |
| name: string | |
| role: "Role" | |
| Division: | |
| name: string | |
| employees: "[]Person" | |
| Role: | |
| name: string | |
| seniority: string | |
| schema: |
|
ping @AlessioDiama - are still iterating on this? |
|
hi @a-hilaly, i am a little bit busy, will try to continue as soon as possible. |
|
/check-cla |
|
@AlessioDiama can you please sign the CLA? |
it is now done. |
jakobmoellerdev
left a comment
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.
For me this needs 2 more things before we can lgtm: I think it would be best if you can include a reference documentation on usage in the website.
I also still think an integration and/or e2e test is useful.
Once we have this, I think this is good for merge!
|
@AlessioDiama: The following tests failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
/milestone 0.7 |
With this PR we add support for recursive references in custom type definitions, i.e. a custom type can reference another custom type. Cyclic references are detected and not allowed.
Tests are extended to cover the new functionality and an example is provided