Improve 'source apiserver update --resource'#590
Improve 'source apiserver update --resource'#590knative-prow-robot merged 3 commits intoknative:masterfrom
Conversation
0254441 to
847a337
Compare
847a337 to
8564205
Compare
maximilien
left a comment
There was a problem hiding this comment.
Thanks for the contributions @daisy-ycguo
See my comments.
/ok-to-test.
| "Resource" send the full resource. (default "Ref") | ||
| -n, --namespace string Specify the namespace to operate in. | ||
| --resource strings Specification for which events to listen, in the format Kind:APIVersion:isController, e.g. Deployment:apps/v1:true. | ||
| --resource stringArray Specification for which events to listen, in the format Kind:APIVersion:isController, e.g. Deployment:apps/v1:true. |
There was a problem hiding this comment.
The example shows a / but the format does not
There was a problem hiding this comment.
Deployment:apps/v1:true is just the format of Kind:APIVersion:isController. APIVersion usually looks like:
serving.knative.dev/v1or- simply
v1, only means kubernetes API V1.
I could change it to v1 if it looks confused.
There was a problem hiding this comment.
I would keep apps/v1 as v1 without an apigroup is more the exception than the rule. Giving a full example makes more sense.
| "Resource" send the full resource. (default "Ref") | ||
| -n, --namespace string Specify the namespace to operate in. | ||
| --resource strings Specification for which events to listen, in the format Kind:APIVersion:isController, e.g. Deployment:apps/v1:true. | ||
| --resource stringArray Specification for which events to listen, in the format Kind:APIVersion:isController, e.g. Deployment:apps/v1:true. |
There was a problem hiding this comment.
same as previous comment. Not / in example but not the format
| Controller: isController, | ||
| } | ||
| b.apiServerSource.Spec.Resources = append(resources, resourceRef) | ||
| return b |
There was a problem hiding this comment.
Is this return of b done to stack the calls to add resource? e.g., AddResource(...).AddResource(...) otherwise it seems weird ad Go code, as matter of style, does not have a return of the instance in a method...
There was a problem hiding this comment.
I follow the same style in other eventing clients, e.g. cronjob_client and eventing_client.
I agree with you that the return is unmeaning, unless @rhuss could give an explanation which we didn't think about. If we all agree to remove the return, I will suggest to create a separate issue to track and we can change them all together.
There was a problem hiding this comment.
This is classical pattern used in many languages which allows for chained calls for elegant, DSL-like APIs. Do you remember the review comment where there was a check for a nil-receiver in a method (in some dependency) ? That is exactly for that use case of chained calls, where an intermediate call could return nil. So its also used in Go quite a bit (and btw is very popular in Java as well).
Some references illustrating that chaining is quite popular in Go, too (and they all return the object itself):
- https://github.com/mauricioklein/go-chainable
- https://vikash1976.wordpress.com/2017/02/03/method-chaining-in-go-lang-through-interface/
- https://riptutorial.com/go/example/13465/chaining-methods
- https://stackoverflow.com/questions/27297702/go-method-chaining-and-error-handling
- ....
BTW, you can still ignore the returned value if you like.
| return b, nil | ||
| } | ||
| } | ||
| return b, fmt.Errorf("cannot find resource %s:%s:%t to remove", version, kind, isController) |
There was a problem hiding this comment.
In this case because of the error you would not able able to stack calls.
There was a problem hiding this comment.
If the resource could be found, the function will return at the line 193 without error.
If the resource could not be found after traversing the whole structure, the function will return at line 196 with an error.
Sorry I don't get your point. Could you help to explain more of your comment ?
There was a problem hiding this comment.
@maximilien is right, chaining works on methods which return a single return value, so returning two values makes no sense in this pattern.
But as we discussed in the other PR having mutating methods on a builder is an anti-pattern (as then the builder turns more into a regular domain objects). Maybe we can solve this use case differently, too ?
If not, we could agree that removing a non-existing resource is just a no-op and gets ignored, so that you can return directly the builder again.
| Controller: isController, | ||
| } | ||
| b.apiServerSource.Spec.Resources = append(resources, resourceRef) | ||
| return b |
There was a problem hiding this comment.
Nit... Also b while short is not very descriptive... perhaps builder is better.
There was a problem hiding this comment.
b is short for builder. Again, I will suggest to create a separate issue to change all client codes, including cronjob_client, eventing_client and apiserver_client.
| return added, removed, nil | ||
| } | ||
|
|
||
| func getValidResource(resource string) (*resourceSpec, error) { |
There was a problem hiding this comment.
you should add comment for all code. I would rather see for public methods and functions but since you added above then should be consistent...
| "Resource" send the full resource.`) | ||
| cmd.Flags().StringSliceVar(&f.Resources, | ||
| cmd.Flags().StringArrayVar(&f.Resources, | ||
| "resource", |
There was a problem hiding this comment.
Why does the name resource not match the variable f.Resources? Should this not be "resources"?
There was a problem hiding this comment.
resource is a flag. Resources is an array of resource.
User uses --resource abc --resource def to set multiple resources, which will be saved in f.Resources.
| Mode: "Ref", | ||
| Resources: []string{"Service:serving.knative.dev/v1alpha1:true"}, | ||
| } | ||
| created, _ := createFlag.getAPIServerResourceArray() |
There was a problem hiding this comment.
why are you ignoring errors? Just assert that they are nil
There was a problem hiding this comment.
will add some tests to test errors. Thank you for pointing out.
| ) | ||
|
|
||
| func TestGetAPIServerResourceArray(t *testing.T) { | ||
| t.Run("get single apiserver resource", func(t *testing.T) { |
There was a problem hiding this comment.
I think since there are many combination you are testing you should offer a small comment summary of the table and what combination you are testing. This would even help you if you need to add or remove tests. Or seperate them into their own t.Run calls
There was a problem hiding this comment.
simple comments are added before test cases. e.g. line 38 and line 52.
But I think maybe separating them into a single t.Run is more readable.
| assert.DeepEqual(t, expectedStringToRemove, stringToRemove) | ||
| } | ||
|
|
||
| func TestAddListAndRemovalListFromArray(t *testing.T) { |
There was a problem hiding this comment.
I would add tests for corner cases. For instance,
- add one item and remove none,
- add no items, remove one,
- add one item and remove one,
- add none and remove none.
|
Is this ready? Also, looks like you have some conflicts to resolve. Thanks. |
467edd9 to
fc74cbb
Compare
| } | ||
| for _, k := range added { | ||
| b.AddResource(k.ApiVersion, k.Kind, k.IsController) | ||
| } |
There was a problem hiding this comment.
I would follow the same scheme here as we had for trigger filters in #603 , I think it covers the same use case.
There was a problem hiding this comment.
Resource is not a string map, but an array. I cannot leverage stringmap here.
I have to think about how to handle.
There was a problem hiding this comment.
Ah, right. Unfortunately, there are no generics in golang, that would make things much easier. Its also not easy to simulate with interfaces ;(
fc74cbb to
6edd8b7
Compare
| } | ||
|
|
||
| // updateExistingAPIServerResourceArray is to update an array of resources. | ||
| func (f *APIServerSourceUpdateFlags) updateExistingAPIServerResourceArray(existing []v1alpha1.ApiServerResource) ([]v1alpha1.ApiServerResource, error) { |
There was a problem hiding this comment.
I would like to move this method to parsing_helper.go to provide a general method UpdateArray to update existing array.
func UpdateArray(existing []interface{}, added []interface{}, removed []interface{}) ([]interface{}, error)
My question is which type (or key word) I should use to describe a general array here ?
I tried to use []interface{} but I get an error:
cannot use existing (type []"knative.dev/eventing/pkg/apis/sources/v1alpha1".ApiServerResource) as type []interface {} in argument to util.UpdateArray
There was a problem hiding this comment.
That's not easily possible, see https://golang.org/doc/faq#convert_slice_of_interface
There was a problem hiding this comment.
What you could do is to introduce a simple utility method which converts an []ApiServerResource to a []interface{} and feed that into the generic method like in
fromISlice(UpdateArray(toISlice(existing), toISlice(added), toISlice(removed)))and do the conversion between ApiServerSource and interfaces slices before and after calling the function. But I don't think its worth the hassle.
There was a problem hiding this comment.
oh, ok. Then I keep as it is.
Please review the latest version. I have removed AddResource and RemoveResource from Builder.
Yes, it's ready for a review. |
rhuss
left a comment
There was a problem hiding this comment.
Some minor comments, otherwise looks good to me.
| } | ||
| added = append(added, *resourceSpec) | ||
| } | ||
| for _, r := range removedArray { |
There was a problem hiding this comment.
you could put the loop into a seperate function and call it twice, for adds and removals. That would reduce duplicated code a bit.
| parts := strings.Split(resource, apiVersionSplitChar) | ||
| parts := strings.SplitN(resource, apiVersionSplitChar, 3) | ||
| if len(parts[0]) == 0 { | ||
| return nil, fmt.Errorf("cannot find KIND in resource specification %s", resource) |
There was a problem hiding this comment.
| return nil, fmt.Errorf("cannot find KIND in resource specification %s", resource) | |
| return nil, fmt.Errorf("cannot find 'Kind' part in resource specification %s (expected: <Kind:ApiVersion[:controllerFlag]>", resource) |
| if len(parts) >= 2 && len(parts[1]) > 0 { | ||
| version = parts[1] | ||
| } else { | ||
| version = "v1" |
There was a problem hiding this comment.
Do we really want a default to "v1" ? I would prefer to be always explicit with the ApiVersion and make it mandatory (as you have to do it for any k8s resource, too, when deploying to the server)
There was a problem hiding this comment.
I just want to make a simple format for ApiServerSource, e.g. Event or Pod or Deployment can be described as --resource Event --resource Pod --resource Deployment which look more artistic, other than --resource Event:v1 --resource Pod:v1 --resource Deployment:v1
So if no ApiVersion provides, I use v1 instead. If user want to use default version but set a controller flag, it could be describe as Event::true.
If you don't agree with this kind of contraction rule, I could delete this feature.
There was a problem hiding this comment.
My point is that this is a bit surprising for Kubernetes users, as the apiVersion is a mandatory field in a resource descriptors. Also, imagine now that the Event or Pod spec evolves to a v2 version. Would you then also update the default to get to this newest version ? Will a user still using v1 and, more important, is she aware of that not the latest event version is used ? Maybe even v1 is getting removed later on then, making the default useless.
I understand that you want to make it as easy and concise as possible. However, its one of key principle of Kubernetes (and hence Knative, too) that to identify a resource you alway need apiVersion + kind.
So I really would be explicit here to (a) be on par with the Kubernetes spec and (b) to avoid future surprises (and (c) to avoid 'hacks' like '::' to get to the third option).
There was a problem hiding this comment.
btw, Deployment is in apps/v1, not v1. There are only a handful of resources that are really in v1.
There was a problem hiding this comment.
oh. Got your point. Let me remove the default value.
There was a problem hiding this comment.
Done. The latest version is submitted and ready for review. @rhuss
| if len(parts) >= 3 && len(parts[2]) > 0 { | ||
| isController, err = strconv.ParseBool(parts[2]) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("cannot parse controller flage in resource specification %s", resource) |
There was a problem hiding this comment.
| return nil, fmt.Errorf("cannot parse controller flage in resource specification %s", resource) | |
| return nil, fmt.Errorf("controller flag is not a boolean in resource specification %s (expected: <Kind:ApiVersion[:controllerFlag]>)", resource) |
cf02e79 to
b812464
Compare
|
/test pull-knative-client-integration-tests |
|
@maximilien @rhuss Are we ready to merge this ? I'm on vacation. I could still address the comments if you have. Thank you. |
b812464 to
1276fbf
Compare
|
The following is the coverage report on the affected files.
|
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: daisy-ycguo, maximilien, rhuss The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
* Update kn-plugin-source-kafka to v0.19.0 * Enable Kafka for E2E tests * Update vendor dir
Fixes #565
Proposed Changes
source apiserver update --resourceto follow the common update semantics-