Skip to content
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

Improve 'source apiserver update --resource' #590

Merged
merged 3 commits into from
Jan 28, 2020

Conversation

daisy-ycguo
Copy link
Member

Fixes #565

Proposed Changes

  • Improve source apiserver update --resource to follow the common update semantics
  • Support remove resource when suffixed with -

@googlebot googlebot added the cla: yes Indicates the PR's author has signed the CLA. label Dec 23, 2019
@knative-prow-robot knative-prow-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Dec 23, 2019
Copy link
Contributor

@maximilien maximilien left a comment

Choose a reason for hiding this comment

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

Thanks for the contributions @daisy-ycguo
See my comments.
/ok-to-test.

@@ -26,7 +26,7 @@ kn source apiserver create NAME --resource RESOURCE --service-account ACCOUNTNAM
"Ref" sends only the reference to the resource,
"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.
Copy link
Contributor

Choose a reason for hiding this comment

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

The example shows a / but the format does not

Copy link
Member Author

Choose a reason for hiding this comment

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

Deployment:apps/v1:true is just the format of Kind:APIVersion:isController. APIVersion usually looks like:

  • serving.knative.dev/v1 or
  • simply v1, only means kubernetes API V1.

I could change it to v1 if it looks confused.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep apps/v1 as v1 without an apigroup is more the exception than the rule. Giving a full example makes more sense.

@@ -26,7 +26,7 @@ kn source apiserver update NAME --resource RESOURCE --service-account ACCOUNTNAM
"Ref" sends only the reference to the resource,
"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.
Copy link
Contributor

Choose a reason for hiding this comment

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

same as previous comment. Not / in example but not the format

Copy link
Member Author

Choose a reason for hiding this comment

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

see my previous comments

Controller: isController,
}
b.apiServerSource.Spec.Resources = append(resources, resourceRef)
return b
Copy link
Contributor

Choose a reason for hiding this comment

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

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...

Copy link
Member Author

@daisy-ycguo daisy-ycguo Jan 9, 2020

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

@rhuss rhuss Jan 10, 2020

Choose a reason for hiding this comment

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

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):

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

Choose a reason for hiding this comment

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

In this case because of the error you would not able able to stack calls.

Copy link
Member Author

@daisy-ycguo daisy-ycguo Jan 9, 2020

Choose a reason for hiding this comment

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

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 ?

Copy link
Contributor

@rhuss rhuss Jan 15, 2020

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

Nit... Also b while short is not very descriptive... perhaps builder is better.

Copy link
Member Author

Choose a reason for hiding this comment

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

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 &resourceList, nil
return added, removed, nil
}

func getValidResource(resource string) (*resourceSpec, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

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...

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a comment.

@@ -95,9 +114,9 @@ func (f *APIServerSourceUpdateFlags) Add(cmd *cobra.Command) {
`The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
"Resource" send the full resource.`)
cmd.Flags().StringSliceVar(&f.Resources,
cmd.Flags().StringArrayVar(&f.Resources,
"resource",
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does the name resource not match the variable f.Resources? Should this not be "resources"?

Copy link
Member Author

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

why are you ignoring errors? Just assert that they are nil

Copy link
Member Author

Choose a reason for hiding this comment

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

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) {
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 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

Copy link
Member Author

@daisy-ycguo daisy-ycguo Jan 9, 2020

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

@@ -92,3 +92,9 @@ func TestParseMinusSuffix(t *testing.T) {
assert.DeepEqual(t, expectedMap, inputMap)
assert.DeepEqual(t, expectedStringToRemove, stringToRemove)
}

func TestAddListAndRemovalListFromArray(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add tests for corner cases. For instance,

  1. add one item and remove none,
  2. add no items, remove one,
  3. add one item and remove one,
  4. add none and remove none.

Copy link
Member Author

Choose a reason for hiding this comment

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

will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

@knative-prow-robot knative-prow-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 7, 2020
@maximilien
Copy link
Contributor

Is this ready? Also, looks like you have some conflicts to resolve. Thanks.

@daisy-ycguo daisy-ycguo force-pushed the updateapiserver branch 2 times, most recently from 467edd9 to fc74cbb Compare January 15, 2020 09:00
}
for _, k := range added {
b.AddResource(k.ApiVersion, k.Kind, k.IsController)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would follow the same scheme here as we had for trigger filters in #603 , I think it covers the same use case.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok. will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

Resource is not a string map, but an array. I cannot leverage stringmap here.
I have to think about how to handle.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, right. Unfortunately, there are no generics in golang, that would make things much easier. Its also not easy to simulate with interfaces ;(

Copy link
Contributor

@rhuss rhuss left a comment

Choose a reason for hiding this comment

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

thanks ! lgtm but I would remove the 'remove' method from the builder and model it the same as in #603

@knative-prow-robot knative-prow-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jan 15, 2020
}

// updateExistingAPIServerResourceArray is to update an array of resources.
func (f *APIServerSourceUpdateFlags) updateExistingAPIServerResourceArray(existing []v1alpha1.ApiServerResource) ([]v1alpha1.ApiServerResource, error) {
Copy link
Member Author

@daisy-ycguo daisy-ycguo Jan 15, 2020

Choose a reason for hiding this comment

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

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

@rhuss rhuss Jan 15, 2020

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

@daisy-ycguo daisy-ycguo Jan 15, 2020

Choose a reason for hiding this comment

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

oh, ok. Then I keep as it is.
Please review the latest version. I have removed AddResource and RemoveResource from Builder.

@daisy-ycguo
Copy link
Member Author

Is this ready? Also, looks like you have some conflicts to resolve. Thanks.

Yes, it's ready for a review.

Copy link
Contributor

@rhuss rhuss left a comment

Choose a reason for hiding this comment

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

Some minor comments, otherwise looks good to me.

}
added = append(added, *resourceSpec)
}
for _, r := range removedArray {
Copy link
Contributor

Choose a reason for hiding this comment

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

you could put the loop into a seperate function and call it twice, for adds and removals. That would reduce duplicated code a bit.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

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

Choose a reason for hiding this comment

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

Suggested change
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)

Copy link
Member Author

Choose a reason for hiding this comment

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

done

if len(parts) >= 2 && len(parts[1]) > 0 {
version = parts[1]
} else {
version = "v1"
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 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)

Copy link
Member Author

@daisy-ycguo daisy-ycguo Jan 16, 2020

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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).

Copy link
Contributor

Choose a reason for hiding this comment

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

btw, Deployment is in apps/v1, not v1. There are only a handful of resources that are really in v1.

Copy link
Member Author

Choose a reason for hiding this comment

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

oh. Got your point. Let me remove the default value.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. The latest version is submitted and ready for review. @rhuss

}
version := parts[1]
if len(parts) >= 3 {
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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)

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@daisy-ycguo daisy-ycguo force-pushed the updateapiserver branch 2 times, most recently from cf02e79 to b812464 Compare January 16, 2020 15:17
@knative-prow-robot knative-prow-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jan 16, 2020
@maximilien
Copy link
Contributor

/test pull-knative-client-integration-tests

@daisy-ycguo
Copy link
Member Author

daisy-ycguo commented Jan 21, 2020

@maximilien @rhuss Are we ready to merge this ? I'm on vacation. I could still address the comments if you have. Thank you.

@knative-metrics-robot
Copy link

The following is the coverage report on the affected files.
Say /test pull-knative-client-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/kn/commands/source/apiserver/create.go 83.9% 84.8% 1.0
pkg/kn/commands/source/apiserver/flags.go 66.2% 75.2% 9.0
pkg/util/parsing_helper.go 86.4% 88.2% 1.9

Copy link
Contributor

@rhuss rhuss left a comment

Choose a reason for hiding this comment

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

thanks, lgtm !

/lgtm

@knative-prow-robot knative-prow-robot added the lgtm Indicates that a PR is ready to be merged. label Jan 28, 2020
@knative-prow-robot
Copy link
Contributor

[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

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@knative-prow-robot knative-prow-robot merged commit 00da006 into knative:master Jan 28, 2020
dsimansk added a commit to dsimansk/client that referenced this pull request Jan 27, 2021
* Update kn-plugin-source-kafka to v0.19.0

* Enable Kafka for E2E tests

* Update vendor dir
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cla: yes Indicates the PR's author has signed the CLA. lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

source apiserver update --resource should follow the common update semantics for list like structures
7 participants