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

Add a /refresh endpoint for forcing an update in an AppRepository #2138

Merged
merged 7 commits into from
Nov 9, 2020

Conversation

antgamdia
Copy link
Contributor

Description of the change

As discussed in #2062 , an endpoint to trigger a refresh in a given repository was requested. Internally, a refresh is being triggered when resyncRequests property changes. This PR creates an endpoint for doing so.
It also has implications on the frontend side, since it is no longer need to use de old logic (GET the appRepo, increase the resyncRequests and PUT it back).

Benefits

This PR enables third-party integrations (i.e., Harbor webhooks) to manually trigger an update in a given AppRepository whenever needed.
Furthermore, on the frontend side, we no longer need to perform two requests, one is enough.

Possible drawbacks

I can't see any negative implications in the short term. Perhaps we should re-think webhook and subscription mechanisms in the future and revisit this /refresh approach

Applicable issues

Additional information

For example, a POST /api/v1/clusters/default/namespaces/kubeapps/apprepositories/bitnami/refresh will trigger a refresh in the bitnami appRepository in the namespace kubeapps.

Rel vmware-tanzu#2062
For instance, "POST /api/v1/clusters/default/namespaces/kubeapps/apprepositories/bitnami/refresh" will trigger a refresh in the "bitnami" appRepository in the namespace "kubeapps"
Rel vmware-tanzu#2062
Now it calls the new POST .../refresh endpoint implemented in the backend.
Copy link
Contributor

@absoludity absoludity left a comment

Choose a reason for hiding this comment

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

This PR enables third-party integrations (i.e., Harbor webhooks) to manually trigger an update in a given AppRepository whenever needed.

Although I like the generals changes here (simplifying refresh), I just want to check: how does this work as a third-party webhook integration, when it depends on the user's authentication? Is the harbor webhook only ever triggered via the users browser session as opposed to a background process?

That is, the new endpoint assumes the user is logged in to kubeapps (so that their cookie is being used by oauth2-proxy to include their id_token as the bearer, etc.). I assume if you hit this URL via curl or a private browser window, it'd not work (at least I hope it wouldn't currently :) ). So it may work if the webhook is triggered from the users' browser in harbor and they're already auth'd with Kubeapps, but I'm guessing otherwise it'd fail (ie. logout of kubeapps and then try from harbor).

We could enable the refresh to happen without auth creds with specific conditions (since it's idempotent and non-destructive) - interested to hear your thoughts, or where I've misunderstood :)

@@ -82,6 +82,8 @@ export const backend = {
validate: (cluster: string) => `${backend.apprepositories.base(cluster, "kubeapps")}/validate`,
delete: (cluster: string, name: string, namespace: string) =>
`${backend.apprepositories.base(cluster, namespace)}/${name}`,
refresh: (cluster: string, name: string, namespace: string) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

I was going to say that the ordering is odd - I've been trying to standardise on cluster, namespace, name in other places - but I see you're just following the existing code here. Perhaps in a followup PR we can standardise these ones to match?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, below the update method is properly cluster, namespace, name so I would use that order.

Copy link
Contributor Author

@antgamdia antgamdia Nov 4, 2020

Choose a reason for hiding this comment

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

+1 the ordering is pretty odd. I considered changing to cluster, namespace, name, but I opted for preserving the existing order for the sake of uniformity.
I'll send another PR to solve it when this one becomes merged, or do you prefer to fix it directly in this PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

both options work for me

@@ -274,12 +305,14 @@ func SetupDefaultRoutes(r *mux.Router, clustersConfig kube.ClustersConfig) error
r.Methods("PUT").Path("/namespaces/{namespace}/apprepositories/{name}").Handler(http.HandlerFunc(UpdateAppRepository(backendHandler)))
r.Methods("DELETE").Path("/namespaces/{namespace}/apprepositories/{name}").Handler(http.HandlerFunc(DeleteAppRepository(backendHandler)))
r.Methods("GET").Path("/namespaces/{namespace}/operator/{name}/logo").Handler(http.HandlerFunc(GetOperatorLogo(backendHandler)))

Copy link
Contributor

Choose a reason for hiding this comment

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

:) I'm liking your attention both to detail and clarity.

Copy link
Contributor

@andresmgot andresmgot left a comment

Choose a reason for hiding this comment

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

Thanks @antgamdia! I have a few comments.

const { data } = await axiosWithAuth.put(
AppRepository.getSelfLink(cluster, namespace, name),
repo,
const { data } = await axiosWithAuth.post(
Copy link
Contributor

Choose a reason for hiding this comment

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

You can do a get request, right?

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, technically it is perfectly feasible. However, webhooks (almost) always use POST method for sending requests mainly for: I) semantically, "refresh" is an action and it is something you want to get executed, not simply retrieve a value; II) it will trigger a subsequent operation that may not be idempotent.

At the moment, this POST method will ignore any payload sent in the body, that's the reason for sending null at the frontend.

Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 okay, makes sense

@@ -82,6 +82,8 @@ export const backend = {
validate: (cluster: string) => `${backend.apprepositories.base(cluster, "kubeapps")}/validate`,
delete: (cluster: string, name: string, namespace: string) =>
`${backend.apprepositories.base(cluster, namespace)}/${name}`,
refresh: (cluster: string, name: string, namespace: string) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, below the update method is properly cluster, namespace, name so I would use that order.

Comment on lines 449 to 452
if a.kubeappsNamespace == "" {
log.Errorf("attempt to use app repositories handler without kubeappsNamespace configured")
return nil, fmt.Errorf("kubeappsNamespace must be configured to enable app repository handler")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

you don't need this check for this method (you don't need a.kubeappsNamespace here).

r.Methods("GET").Path("/clusters/{cluster}/namespaces").Handler(http.HandlerFunc(GetNamespaces(backendHandler)))
r.Methods("GET").Path("/clusters/{cluster}/apprepositories").Handler(http.HandlerFunc(ListAppRepositories(backendHandler)))
r.Methods("GET").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories").Handler(http.HandlerFunc(ListAppRepositories(backendHandler)))
r.Methods("POST").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories").Handler(http.HandlerFunc(CreateAppRepository(backendHandler)))
r.Methods("POST").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories/validate").Handler(http.HandlerFunc(ValidateAppRepository(backendHandler)))
r.Methods("PUT").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories/{name}").Handler(http.HandlerFunc(UpdateAppRepository(backendHandler)))
r.Methods("POST").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories/{name}/refresh").Handler(http.HandlerFunc(RefreshAppRepository(backendHandler)))
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 change this for a GET request, the body is not relevant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See my comments above

@andresmgot
Copy link
Contributor

Although I like the generals changes here (simplifying refresh), I just want to check: how does this work as a third-party webhook integration, when it depends on the user's authentication? Is the harbor webhook only ever triggered via the users browser session as opposed to a background process?

That is, the new endpoint assumes the user is logged in to kubeapps (so that their cookie is being used by oauth2-proxy to include their id_token as the bearer, etc.). I assume if you hit this URL via curl or a private browser window, it'd not work (at least I hope it wouldn't currently :) ). So it may work if the webhook is triggered from the users' browser in harbor and they're already auth'd with Kubeapps, but I'm guessing otherwise it'd fail (ie. logout of kubeapps and then try from harbor).

We could enable the refresh to happen without auth creds with specific conditions (since it's idempotent and non-destructive) - interested to hear your thoughts, or where I've misunderstood :)

Yes, with these changes, only authenticated users (with permissions to modify AppRepos) would be able to trigger the refresh. My suggestion is to write some documentation about how to create a serviceaccount with those permissions and use its token as a Bearer token when configuring, for example, the Harbor webhook (as explained in here: #2062 (comment)).

@antgamdia
Copy link
Contributor Author

My suggestion is to write some documentation about how to create a serviceaccount with those permissions and use its token as a Bearer token when configuring,

+1 to this idea. Harbor indeed only supports authentication in webhooks by manually setting the HTTP Authorization header when configuring it. Therefore, I'll go for the idea of the serviceaccount for hooks.
I'll investigate and document it in this PR.

@andresmgot
Copy link
Contributor

I'll investigate and document it in this PR.

You can do a separate PR for it if you want. Typically split PRs are better.

Copy link
Contributor

@absoludity absoludity left a comment

Choose a reason for hiding this comment

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

My suggestion is to write some documentation about how to create a serviceaccount with those permissions and use its token as a Bearer token when configuring,

+1 to this idea. Harbor indeed only supports authentication in webhooks by manually setting the HTTP Authorization header when configuring it. Therefore, I'll go for the idea of the serviceaccount for hooks.
I'll investigate and document it in this PR.

Great - sounds good. Small concern that the permission required by an external webhook would be to edit AppRepository resources (not just the count field, so for example, the cred could be mis-used to change the source of the app repo), but not a big issue right now, and something that will be solved by open policy agent in the not-too-distant future (well, already, it's just not common to use it extensively yet).

appRepo, err := a.clientset.KubeappsV1alpha1().AppRepositories(requestNamespace).Get(context.TODO(), repoName, metav1.GetOptions{})
if err != nil {
return nil, err
}

// an updated is forced if the ResyncRequests property changes,
// An updated is forced if the ResyncRequests property changes,
Copy link
Contributor

Choose a reason for hiding this comment

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

An update

@@ -80,8 +80,10 @@ export const backend = {
backend.apprepositories.base(cluster, namespace),
list: (cluster: string, namespace: string) => backend.apprepositories.base(cluster, namespace),
validate: (cluster: string) => `${backend.apprepositories.base(cluster, "kubeapps")}/validate`,
delete: (cluster: string, name: string, namespace: string) =>
delete: (cluster: string, namespace: string, name: string) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

you have changed the order of the parameters here in this delete but not in the places it's used, maybe you should leave this change out of this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally agree, it was wrongly staged and committed while working on #2147. My fault!

Also fix minor typo
Since refreshing an apprepository no longer requires to get the repo to send an edition later, this test step (faulty) can be removed
Conflicts:
	dashboard/src/shared/AppRepository.ts
@antgamdia
Copy link
Contributor Author

Comments from this PR addressed. Also edited a test step that is no longer required. Merged the latest master commits to integrate changes on #2147.

@antgamdia antgamdia mentioned this pull request Nov 6, 2020
@antgamdia antgamdia merged commit 3c5fdf4 into vmware-tanzu:master Nov 9, 2020
@antgamdia antgamdia deleted the 2062-repoRefreshHook branch November 9, 2020 08:01
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.

[FEATURE REQUEST] refresh repository by webhook
3 participants