-
Notifications
You must be signed in to change notification settings - Fork 705
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
Move all k8s resources operations to pkg/apprepo #1518
Conversation
This is only for the GetNamespaces method, right? The way you've stated it here sounds like it's general functionality for the package (ie. including other methods like CreateAppRepository etc.) |
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'm keen for any call-sites to be super clear as to whether they're getting user resources (app repos or secrets) using the user token or the service account, so any future reviews or changes can't let something slip in that is incorrectly using the service account token.
For the namespaces this is not important, imo, as we explicitly want to enable exposing just the namespaces to which the user has access. But for these general getters on actual user resources, it's much more important, imo.
return | ||
kubeappsNamespace := os.Getenv("POD_NAMESPACE") | ||
if namespace == "" { | ||
namespace = defaultNamespace |
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 are we defaulting/guessing that the Kubeapps namespace is the kube-system
here (metav1.NamespaceSystem
)?
We should now be able to fail to start, given that POD_NAMESPACE
is set for both tiller-proxy and kubeops? EDIT: Ah, because this is within a function which has no option of returning an error :/ . If WithHandlerConfig
requires knowing what the kubeapps namespace is, let's make it a required arg?
EDIT2: And because you've not changed this, it was already like this in pkg/chart
from where it was moved. It seems like a bad guess to make, potentially a security issue.
My vote would be to pass it as a required arg (and in the main.go, fail if it's not defined). Let me know if you've got other ideas.
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.
yes, I just moved code around. It doesn't really make sense for this to be kube-system
in any case so I am fine moving this to the main.go file
pkg/apprepo/apprepos_handler.go
Outdated
configCopy.BearerToken = token | ||
if token != "" { | ||
configCopy.BearerToken = token | ||
} |
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 looks dangerous - as though it implicitly returns a service account config if you pass in an empty token? ConfigForToken
should only ever return a config for the specified token, whether or not the token is passed with bad information.
pkg/apprepo/apprepos_handler.go
Outdated
// GetAppRepository returns an AppRepository resource from a namespace. | ||
// Optionally set a token to get the AppRepository using a custom serviceaccount | ||
func (a *AppRepositoriesHandler) GetAppRepository(repoName, repoNamespace, token string) (*v1alpha1.AppRepository, error) { | ||
clientset, err := a.clientsetForRequest(token) |
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.
Right, this looks like we're depending on clientsetForRequest("")
returning the service account clientset. I'm -1 on that, it's too easy to mistakenly end up with a service account client set. I think we should remove that and do something like:
if token != "" {
clientset, err := a.clientsetForToken(token)
} else {
clientset = svcKubeClient
}
In fact, even then the optional token on this public method is very implicit at call-sites. We could additionally make this fn private (getAppRepository
) and instead expose two very small (single-line) wrappers:
func (a *AppRepositoriesHandler) GetAppRepositoryForUser(repoName, repoNamespace, token string) ...
and
func (a *AppRepositoriesHandler) GetAppRepositoryForServiceAccount(repoName, repoNamespace string) ...
calling the private method.
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.
My plan was to standardize this library to have always this token
property to be able to reuse all the methods (even most of them are currently just used with or without token).
Another possibility, to keep having the flexibility that the code have now and being explicit we can add another interface to make sure that the caller is using the desired service account:
handler.AsUser(token).CreateAppRepository(...)
handler.AsSVC().GetSecret(...)
pkg/apprepo/apprepos_handler.go
Outdated
|
||
// GetSecret return the a secret from a namespace using a token if given | ||
func (a *AppRepositoriesHandler) GetSecret(name, namespace, token string) (*corev1.Secret, error) { | ||
userClientset, err := a.clientsetForRequest(token) |
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.
Same here.
pkg/chart/chart.go
Outdated
// We grab the specified app repository (for later access to the repo URL, as well as any specified | ||
// auth). | ||
appRepo, err := c.appRepoClient.KubeappsV1alpha1().AppRepositories(namespace).Get(details.AppRepositoryResourceName, metav1.GetOptions{}) | ||
appRepo, err := c.appRepoHandler.GetAppRepository(details.AppRepositoryResourceName, c.kubeappsNamespace, "") |
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 this (and the calls below) would use .GetAppRepositoryForServiceAccount(details.AppRepositoryResourceName, c.kubeappsNamespace)
9391ef1
to
d6234fd
Compare
d6234fd
to
7a839b5
Compare
Thanks for the review @absoludity. Check 7a839b5, I think we can have both flexibility and explicit token usage as I explain in a inline comment. Let me know what you think about it. |
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 Andres. I'm adding a +1, as this is much nicer with the explicit handlers. I do have a couple of concerns inline. You may be able to resolve them with your current approach using AsUser()
and AsSVC
, or it may be simpler to just have two explicit methods GetNamespaces
and GetNamespacesAsSVC
for just the methods which require auth with the SVC account. Hopefully you can find a way with your current approach, as it is nicer :)
kubeappsNamespace := os.Getenv("POD_NAMESPACE") | ||
if kubeappsNamespace == "" { | ||
log.Fatalf("POD_NAMESPACE should be defined") | ||
} |
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.
Much better, thanks for moving it out to main.go even though it wasn't a change you'd added.
// AuthHandler exposes handler functionality as a user or the current serviceaccount | ||
type AuthHandler interface { | ||
AsUser(token string) handler | ||
AsSVC() handler |
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.
Excellent, much more explicit while still being easy to use for call-sites. Thanks!
The only down-side I can think of here is that it now means that all the methods on the interface can be used with the service account, rather than just GetNamespaces
and GetAppRepository
. We could fix that by composing the interface and having AsSVC() return the more limited interface? Not sure. See what you think.
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.
That's what I wanted, if in the future we need to use this Handler with any the svc account or the user account we don't need to modify the library. Since it's quite explicit now, I don't think there is risk for a developer to misuse the method (nor a reviewer to miss it).
pkg/apprepo/apprepos_handler.go
Outdated
a.clientset = clientset | ||
return a | ||
} | ||
|
||
func (a *appRepositoriesHandler) AsSVC() handler { | ||
a.clientset = a.svcClientset | ||
return a |
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.
These don't look thread safe? Would it be better to return a copy configured with the correct clientset, rather than updating the internal a.clientset
for the call-site?
Or, stepping back, it may even make more sense if the current appRepositoryHandler
was something like a handler factory, that only created a handler when you call AsUser
or AsSVC
. Not sure about needing a factory, but the main thing is that it seems in its current form here to be mixing responsibilities... more below.
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.
that's a good point, let me check the possibility of modifying the interfaces to fix this and you next comment.
config: *config, | ||
kubeappsNamespace: kubeappsNamespace, | ||
// See comment in the struct defn above. | ||
clientsetForConfig: clientsetForConfig, | ||
svcKubeClient: svcKubeClient, | ||
svcClientset: &combinedClientset{svcAppRepoClient, svcKubeClient}, |
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 a.clientset
isn't yet defined (as no one has called AsUser()
or AsSVC()
yet), so if a call-site now called a.CreateAppRepository(..)
they'd get a run-time panic, I think? This is what I meant above about mixing responsibilities. Perhaps what is returned here should not implement the interface, and only the handler returned by AsUser()
or AsSVC()
should implement the interface. So the object returned here wouldn't be a handler, but enables you to create a handler.
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.
agree
Merging! let me know if you want to discuss something further |
LGTM, thanks! |
Description of the change
Requires #1517
The goal of this PR is to complete the migration to the existing operations over k8s resources to the package
pkg/apprepo
(the name of the package is to be changed). The advantage of this package is that it allow callers to set atoken
. If set, it uses that token to do requests, if not it uses the backend serviceaccount.Now the package
pkg/chart
uses this package thanks to the implementation of the methodsGetAppRepository
andGetSecret
that use the same approach for thetoken
.The fake Handler has been moved to its own package so it can be reused.