-
Notifications
You must be signed in to change notification settings - Fork 52
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
Ipalloc newKO #123
Ipalloc newKO #123
Conversation
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 propose not to create an interface for each of our K8s resources with 3 methods for each of its fields, and keep the interface and the K8s API go struct in sync later, but use generic methods instead to read/write data into the kpt package.
This means that the implementation of business logic in our kpt functions will use the go structs of those K8s resources that we have to maintain anyway. All validation logic, setter functions, etc. should be added to those structs as opposed to the interfaces.
Here is a proof-of-concept implementation for this proposal:
https://github.com/nokia/nephio-nephio/tree/setspec/krm-functions/setspec-test
Notes on the implementation:
- the repo above contains a kpt function implementation that showcases how both the current SDK and my proposal is used in a kpt function implementation
- in this file: https://github.com/nokia/nephio-nephio/blob/setspec/krm-functions/setspec-test/testdata/test1/_expected.yaml
you can find the output of the function: the resource named "currentsdk" is manipulated with the current SDK, and the other using my proposal. (You can check if they really generated the results in _expected.yaml by actually running the go test) - as you can see this PoC implementation proposal is almost equivalent in terms of keeping the comments with the current SDK
- both versions will keep all comments in Release 1 supported scenarios
- the implementation of SetSpec() is a temporary fix, the proper place to implement this is in the underlying kpt function SDK that we use, more precisely here: https://github.com/GoogleContainerTools/kpt-functions-sdk/blob/e8e9cb3c3ae2a19c22f52701d1542cf24e541df0/go/fn/internal/map.go#L138
at the line that reads: // TODO: Copy comments? :))
Unfortunately different CLA (+ my company's policy) prevents me to contribute there - naturally SetSpec() should be generalized and used for Status, as well (edited)
parser.Parser[*ipamv1alpha1.IPAllocation] | ||
|
||
SetSpec(*ipamv1alpha1.IPAllocationSpec) error | ||
// GetPrefixKind returns the prefixKind from the spec |
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 propose to remove all GetXXX() methods from the interface because of the following reasons:
This functionality is provided by the kpt-fn go SDK that we build on (github.com/GoogleContainerTools/kpt-functions-sdk/go/fn), not just for specific types, but in a generic manner to any type (let it be ipamv1alpha1.IPAllocation, or DataNetworkSpec, or any Kubernetes resource or subresource go types.
So, instead of adding these methods into this interface, we can just do this:
var ipAllocStruct ipamv1alpha1.IPAllocation
err = ipAllocObj.As(&ipAllocStruct)
and then just use the fields of ipAllocStruct
// if an error occurs or the attribute is not present an empty string is returned | ||
GetAllocatedGateway() string | ||
// SetPrefixKind sets the prefixKind in the spec | ||
SetPrefixKind(ipamv1alpha1.PrefixKind) error |
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 propose to remove all SetXXX() and DeleteXXX() methods that sets one of the fields of the Spec, and replace it with a generic function that works for any Kubernetes Resource type that has a spec
field. This would update the whole Spec at once, preserving as much comments as possible (all if nothing was changed). The advantage of this would be that we don't have to reimplement this functionality for each field of each type again, in these interfaces like IPAllocation.
Simply craft a new ipamv1alpha1.IPAllocationSpec struct by optionally getting the current spec field values by the method described above (KubeObject.As()), and then updating the fields of the go struct, and then just use the new SetSpec() generic function on the corresponding fn.KubeObject, (or just use the SetSpec() method of this IPAllocation interface)
var ipAllocObj fn.KubebObject = ...
var ipAllocSpec ipamv1alpha1.IPAllocationSpec
// set the fields of the spec to the desired state
ipAllocSpec.Index = 0
// ...
err := SetSpec(ipAllocObj, &ipAllocSpec)
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 easiest implementation of SetSpec() and SetStatus() would be to send a Pull Request to the underlying SDK that fixes this TODO and copies the comments recursively :)
func (o *MapVariant) setYAMLNode(key string, node *yaml.Node) {
children := o.node.Content
if len(children)%2 != 0 {
log.Fatalf("unexpected number of children for map %d", len(children))
}
for i := 0; i < len(children); i += 2 {
keyNode := children[i]
k, ok := asString(keyNode)
if ok && k == key {
// TODO: Copy comments?
oldNode := children[i+1]
children[i+1] = node
children[i+1].FootComment = oldNode.FootComment
children[i+1].HeadComment = oldNode.HeadComment
children[i+1].LineComment = oldNode.LineComment
return
}
}
o.node.Content = append(o.node.Content, buildStringNode(key), node)
}
// SetCreatePrefix sets the create prefix in the spec | ||
SetCreatePrefix(bool) error | ||
// SetAllocatedPrefix sets the allocated prefix in the status | ||
SetAllocatedPrefix(string) error |
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 propose to remove all SetXXX() and DeleteXXX() methods that sets one of the fields of the Status, and replace it with a generic function that works for any Kubernetes Resource that has a status
field. This would update the whole status
at once, preserving as much comments as possible (all if nothing was changed). The advantage of this would be that we don't have to reimplement this functionality for each field of each type again in these interfaces like IPAllocation.
Simply craft a new ipamv1alpha1.IPAllocationStatus struct by optionally getting the current status field values by the method described above (KubeObject.As()), and then updating the fields of the go struct, and then just use the new SetStatus() generic function on the corresponding fn.KubeObject:
var ipAllocObj fn.KubebObject = ...
var ipAllocStatus ipamv1alpha1.IPAllocationStatus
// set the fields of the spec to the desired state
ipAllocStatus.Gateway = "..."
err := SetStatus(ipAllocObj, &ipAllocStatus)
// NewFromYaml creates a new parser interface | ||
// It expects raw byte slice as input representing the serialized yaml file | ||
func NewFromYAML(b []byte) (IPAllocation, error) { | ||
p, err := parser.NewFromYaml[*ipamv1alpha1.IPAllocation](b) |
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 github.com/GoogleContainerTools/kpt-functions-sdk/go/fn SDK that we use parses all YAML files before calling our "main" function (actually called Run()). It wraps the parsed YAML nodes into a hierarchy of KubeObject and SubObject instances. In order to avoid duplicate parsing, parsing from a YAML file (byte slice) should always be replaced by converting from a KubeObject, e.g. in this case by using the NewFromKubeObject() method of this interface.
"fmt" | ||
|
||
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn" | ||
"github.com/example.com/foo/pkg/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.
The pull request doesn't build on my machine due to this example.com import.
Should it be replaced with something like:
github.com/nephio-project/nephio/krm-functions/lib/parser ?
|
||
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn" | ||
"github.com/example.com/foo/pkg/parser" | ||
ipamv1alpha1 "github.com/nokia/k8s-ipam/apis/alloc/ipam/v1alpha1" |
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 latest tag on github.com/nokia/k8s-ipam is v.0.0.3, and that version doesn't contain the apis/alloc/ipam/v1alpha1
path (but the main
branch does).
That confuses go mod tidy
, since for go the latest module is v0.0.3, and not the main
branch.
Could you add a new tag to the k8s-ipam repo to make it easier to import?
Please see my comments on #121 which is base for others. I beleive if we take that path implementation of these individual libraries will be simpler. |
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, I am more aligned with Istvan on getting rid of the getters and setters. The idea would be that instead of getters and setters, if you want the convenience of using these in strongly typed Go structs, you unmarshal it (GetGoStruct
), then you call set SetFromGoStruct
, which sets the values on the underlying KubeObject, rather than just marshalling it.
I think this would work to preserve comments. The usage would be something like:
ifcWrapper := interfacev1alpha1.NewFromYaml(b)
ifc := ifcWrapper.GetGoStruct() // or maybe the more traditional pass in a pointer
ifc.CNIType = "foo"
ifcWrapper.SetFromGoStruct(ifc)
So - we have ONE getter that returns the Go struct, ONE setter that accepts the Go struct. The underlying KubeObject is updated based on the contents of the Go struct.
This is very close to that; all that is missing is the SetFromGoStruct
(and to reinstate GetGoStruct
).
But I am not going to push, this may just be bike shedding at this point. It's more important to get it merged soon.
We want to go to the gostruct approach but we need to validate that approach if it works with all lists, maps, etc w/o loosing comments when you insert, etc. So this is indeed our final target. We decided to do this in the interim, so we can start testing the fns and in parallel we will go to the go struct approach. |
c24e9e5
to
75a315c
Compare
This aligns the ip allocation with the new KubeObjectExt |
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 @henderiw .
/approve
If I understand correctly, the main reason to have this wrapper at all, is the lack of static type checking in the |
I somehow missed @johnbelamaric 's comment, so far, sorry
I very much like the idea of one |
/lgtm |
The differences between the approaches are very small. KubeObjectExt: expects the developer to know generics and add the right type in the generic field of the go struct. More error prune, but checked in the library. Personally I prefer KubeObjectExt, but the barrier for the consumers is higher. Example Here. Explicit |
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: gvbalaji, henderiw 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 |
/lgtm |
… all/any fields at once (#142) Inspired by [this comment](#123 (review)) from @johnbelamaric : this proposal adds a setter for the whole KubeObjectExt that allows to set all/any fields at once
added ipalloc with new parser interface.