-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Support for rbac v1beta1 on kubernetes v1.7.5+ #682
Conversation
pkg/backup/service_account_action.go
Outdated
"github.com/heptio/ark/pkg/kuberesource" | ||
) | ||
|
||
type clusterRoleBindingVersionWrapper struct { |
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.
Would like some guidance here - I don't think this struct is the way to go, but I was struggling coming up with an interface, given the ClusterRoleBinding is just a struct w/o methods.
9ce51eb
to
0a66211
Compare
pkg/backup/rbac.go
Outdated
// Name returns the name of a ClusterRoleBinding. | ||
Name() string | ||
// Subjects translates a ClusterRoleBindings Subjects slice into a slice of subject wrapper objects. | ||
Subjects() []subject |
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.
One thing you could do is instead of exposing Subjects()
and needing the wrapper type for that, just add a method to the ClusterRoleBinding
interface called HasSubject
that takes a subject kind, namespace, and name, and returns true or false -- since that's all we really need. That would minimize the amount of this wrapper code that we need
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.
Actually may need to be HasServiceAccountSubject
so it doesn't have to take the kind (since that's part of the API group/version), but same idea.
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.
Haven't gone through units in detail yet
pkg/backup/rbac.go
Outdated
rbacclientbeta "k8s.io/client-go/kubernetes/typed/rbac/v1beta1" | ||
) | ||
|
||
type CRBLister interface { |
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 think I'd prefer to spell out ClusterRoleBinding
in the type names (interface + 4 structs). I'm fine using abbreviations for local variable names though
pkg/backup/rbac.go
Outdated
List() ([]ClusterRoleBinding, error) | ||
} | ||
|
||
type V1CRBLister struct { |
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 and the V1beta1CRBLister1
don't need to be exported, just the interface
pkg/backup/rbac.go
Outdated
} | ||
|
||
// NewCRBLister wraps the v1beta1 and v1 RBAC client's List with a common interface. | ||
func NewCRBLister(log logrus.FieldLogger, clientset kubernetes.Interface) (CRBLister, 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.
spell out ClusterRoleBinding
in the func name
pkg/backup/rbac.go
Outdated
|
||
// NewCRBLister wraps the v1beta1 and v1 RBAC client's List with a common interface. | ||
func NewCRBLister(log logrus.FieldLogger, clientset kubernetes.Interface) (CRBLister, error) { | ||
var lister CRBLister |
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.
usually declare local vars closer to their usage (unlike a bunch of other languages)
pkg/backup/rbac.go
Outdated
return nil, errors.WithStack(err) | ||
} | ||
var supportedAPI metav1.GroupVersionForDiscovery | ||
apigroups := discoveryHelper.APIGroups() |
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.
no real need to declare apigroups
- can just range over discoveryHelper.APIGroups()
in the next line
pkg/backup/rbac.go
Outdated
var supportedAPI metav1.GroupVersionForDiscovery | ||
apigroups := discoveryHelper.APIGroups() | ||
for _, ag := range apigroups { | ||
if ag.Name == "rbac.authorization.k8s.io" { |
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.
s/"rbac.authorization.k8s.io"
/rbac.GroupName
pkg/backup/rbac.go
Outdated
|
||
switch supportedAPI.Version { | ||
case "v1": | ||
lister = V1CRBLister{client: clientset.RbacV1().ClusterRoleBindings()} |
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'd just directly return the lister, nil
pkg/backup/rbac.go
Outdated
case "v1": | ||
lister = V1CRBLister{client: clientset.RbacV1().ClusterRoleBindings()} | ||
case "v1beta1": | ||
lister = V1beta1CRBLister{client: clientset.RbacV1beta1().ClusterRoleBindings()} |
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'd just directly return the lister, nil
pkg/backup/rbac.go
Outdated
case "v1beta1": | ||
lister = V1beta1CRBLister{client: clientset.RbacV1beta1().ClusterRoleBindings()} | ||
// Return an empty client if we don't have a known version. | ||
default: |
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'd just directly return nil, nil
for the default (or get rid of default and return it outside the switch)
@@ -1,5 +1,5 @@ | |||
/* | |||
Copyright 2018 the Heptio Ark contributors. | |||
|
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.
undo deletion of copyright
pkg/backup/rbac.go
Outdated
} | ||
|
||
switch supportedAPI.Version { | ||
case "v1": |
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.
could use rbac.SchemeGroupVersion.Version
rather than "v1" here, and rbacbeta.
... for the v1beta1 case
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 had a minor style comment only. There are some exported methods that don't have a description, some are obvious and others not so obvious to me, so my additional comment is to add moar description if it makes sense. Other than this, lgtm as far as I can tell.
pkg/backup/rbac.go
Outdated
} | ||
|
||
func (v1beta1 V1beta1CRBLister) List() ([]ClusterRoleBinding, error) { | ||
var crbs []ClusterRoleBinding |
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.
Wanted to extend on what @skriss said to make sure this and I think one additional case of this is also changed: I'd move this crbs
var to right above the for
loop where it's used. It's a common thing in Go so it's expected but also avoids scrolling all over for where it's used if we expect its first use to happen right after where it's declared.
Which ones in particular? Happy to add more comments, though I'm not sure what's missing. The ones exposed via interfaces should be commented on the interfaces themselves. |
My bad, @nrb, those were precisely the already commented methods of interfaces :) 👍 |
Tested this with Kubernetes v1.7.16. The server started (where it would previously fail), and the cluster role bindings we create with the Ark installation were backed up.
|
pkg/backup/rbac.go
Outdated
} | ||
|
||
func (v1beta1 v1beta1ClusterRoleBindingLister) List() ([]ClusterRoleBinding, 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.
rm blank line
pkg/backup/rbac.go
Outdated
func NewClusterRoleBindingLister(log logrus.FieldLogger, clientset kubernetes.Interface) (ClusterRoleBindingLister, error) { | ||
discoveryHelper, err := arkdiscovery.NewHelper(clientset.Discovery(), log) | ||
if err != nil { | ||
return nil, errors.WithStack(err) |
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.
don't need a errors.WithStack
here since the error is coming from ark code so should already have a stack
pkg/backup/rbac.go
Outdated
} | ||
|
||
// v1 implementation | ||
type v1crb struct { |
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'd probably spell out ClusterRoleBinding in the type name here
pkg/backup/rbac.go
Outdated
|
||
// v1beta1 implementation | ||
|
||
type v1beta1crb struct { |
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'd probably spell out ClusterRoleBinding in the type name here
pkg/backup/rbac.go
Outdated
return saSubjects | ||
} | ||
|
||
// v1beta1 implementation |
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'd remove this, put a godoc comment on the struct if you want but not necessary
pkg/backup/service_account_action.go
Outdated
roles.Insert(clusterRoleBinding.RoleRef.Name) | ||
break | ||
bindings.Insert(crb.Name()) | ||
roles.Insert(crb.RoleRefName()) |
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.
you can still break once you've found a matching subject
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.
Is it not valid to have multiple service account subjects? Wouldn't we want to back them all up?
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.
We are trying to back up CRBs/CRs that have the current service account item as a subject. Once we know that a given CRB does have this SA as a subject, we're going to back it up. No need to look at all the other service accounts that this CRB applies to at that point.
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.
Added back
pkg/discovery/helper.go
Outdated
@@ -45,6 +45,8 @@ type Helper interface { | |||
// Refresh pulls an updated set of Ark-backuppable resources from the | |||
// discovery API. | |||
Refresh() error | |||
|
|||
APIGroups() []metav1.APIGroup |
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.
add godoc
Double-checking with a 1.9 cluster on GKE, ClusterRoleBindings still back up as expected with this change. |
pkg/cmd/server/plugin/plugin.go
Outdated
cmd.CheckError(err) | ||
|
||
if crbLister != nil { |
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.
What happens if crbLister is null (i.e. RBAC is not enabled on this cluster)? We still go through the code paths of setting up an action item plugin and serving it - seems like there may be a problem there.
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.
Good catch - I had intended to address this and forgot.
Would it make sense to check if the action is nil before adding to the plugin configuration? Or maybe move the NewClusterRoleBindingLister logic into the NewServiceAccount action function?
Also, do we support clusters without RBAC? I didn't think we did, but even if that's the case this behavior needs to be fixed.
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 don't know if we've ever really tried on clusters without RBAC. Hopefully shouldn't be much of a use case going fwd, but who knows.
I'd say it makes sense to always have a non-nil action. We can move the NewClusterRoleBindingLister
into the NewServiceAccount
func. If no API group is found, just leave the item action's clusterRoleBindings
field as nil, and return the action/no error. Then Execute()
becomes a no-op (more or less) in that scenario.
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.
Sorry I didn't notice this earlier
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.
np, better now than after tagging!
That logic makes sense. I'll have to modify the signature and the testing fakes a bit, but shouldn't be too bad.
Apart from the question re: plugin serving, the rest LGTM. |
dbf87d2
to
6b2f56f
Compare
pkg/backup/rbac.go
Outdated
} | ||
|
||
// NewClusterRoleBindingLister wraps the v1beta1 and v1 RBAC client's List with a common interface. | ||
func NewClusterRoleBindingLister(log logrus.FieldLogger, clientset kubernetes.Interface, discoveryHelper arkdiscovery.Helper) (ClusterRoleBindingLister, 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.
We could change the signature here so instead of taking a clientset
, it takes a map[string]ListerConstructor
, where the keys are RBAC API versions, and the values are functions that return a ClusterRoleBindingLister
. Then, instead of having a switch statement on concrete versions, we just do a lookup into the map for the server's preferred version.
This logic would be easily testable without actually requiring a kubernetes.Interface
, and the correctness of the map (which would be defined in plugin.go
could be determined by inspection.
Food for thought
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 like that idea - are you thinking of pairing that with a no-op lister then?
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.
Yeah, I think that'd be the way to go. So NewServiceAccountAction
would go back to taking a logger and a CRBLister, NewClusterRoleBindingLister
would take a logger, a discovery helper, and that map of constructor funcs, and return a no-op lister if there was no preferred version or no constructor for the preferred version, and then plugin.go
would call NewClusterRoleBindingLister
then pass the result to NewServiceAccountAction
.
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.
Sketching this out, it might look something like
// In rbac.go
type ListerConstructor func() ClusterRoleBindingLister
NewV1clusterrolebindinglister()
NewV1beta1clusterrolebindinglister()
NewRBACDisabledLister
// In plugins.go
listerFuncs := map[string]ListerConstructor {
rbac.SchemeGroupVersion.Version: NewV1clusterrolebindinglister,
rbacbeta.SchemeGroupVersion.Version: NewV1betaclusterrolebindinglister,
"": NewRBACDisabledLister // returns an empty list
}
action, err := NewServiceAccountAction(log, discoveryHelper, listerFuncs)
// In service_item_action.go/NewServiceAccountAction
// apigroup logic moved from NewClusterRoleBindingLister
crbLister := listerFuncs[apigroup.Version]()
crbs, err := crbLister.List()
if err != nil {
return nil, err
}
return &serviceAccountAction{
log: log,
clusterRoleBindings: crbs,
I think it works; the listerFuncs map may also be specified in rbac.go and exported, looking at it here. WDYT?
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.
yep, looks good!
} | ||
|
||
// NewServiceAccountAction creates a new ItemAction for service accounts. | ||
func NewServiceAccountAction(log logrus.FieldLogger, client rbacclient.ClusterRoleBindingInterface) (ItemAction, error) { | ||
clusterRoleBindings, err := client.List(metav1.ListOptions{}) | ||
func NewServiceAccountAction(log logrus.FieldLogger, clusterRoleBindingListers map[string]ClusterRoleBindingLister, discoveryHelper arkdiscovery.Helper) (ItemAction, 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.
It would be nice to have a unit test on this now that it doesn't depend a full clientset. You could fake/mock a couple of ClusterRoleBindingListers
to return a specific list of CRBs each, put them in the clusterRoleBindingListers
arg, then set up a fake discoveryHelper
with the relevant API groups that correspond to what's in the map (easiest way to set up the fake discoveryHelper
is probably by not using the constructor function, but just instantiating directly and setting the apiGroups
to what you need). Then you can verify that the service account action's clusterRoleBindings
match what the expected lister had.
// NewClusterRoleBindingListerMap creates a map of RBAC version strings to their associated | ||
// ClusterRoleBindingLister structs. | ||
// Necessary so that callers to the ClusterRoleBindingLister interfaces don't need the kubernetes.Interface. | ||
func NewClusterRoleBindingListerMap(clientset kubernetes.Interface) map[string]ClusterRoleBindingLister { |
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 think it would be slightly better if this returned a map[string]ListerConstructor
, so that you don't have to construct all of them upfront. This way, only the one that's actually selected is instantiated. I would just inline the constructors as anonymous functions/closures of the clientset
argument.
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.
Is there a problem or cost with instantiating them all, given we have 3? To me, returning them directly simplifies reading the code and reduces the number of types we have to carry around.
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.
ok, not a big deal.
pkg/backup/rbac.go
Outdated
} | ||
|
||
// NewV1beta1ClusterRoleBindingLister returns a ClusterRoleBindingLister for RBAC v1beta1. | ||
func NewV1beta1ClusterRoleBindingLister() ClusterRoleBindingLister { |
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 should be removed
pkg/backup/rbac.go
Outdated
} | ||
|
||
// NewV1ClusterRoleBindingLister returns a ClusterRoleBindingLister for RBAC v1. | ||
func NewV1ClusterRoleBindingLister() ClusterRoleBindingLister { |
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 should be removed
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.
Looking good, just one unused type def to remove.
pkg/backup/rbac.go
Outdated
} | ||
|
||
// ListerConstructor specifies a function which can return a ClusterRoleBindingLister. | ||
type ListerConstructor func() ClusterRoleBindingLister |
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.
can remove this since it's not being used
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.
Done
Signed-off-by: Nolan Brubaker <[email protected]>
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.
LGTM.
@nrb all set to merge? |
Yep, ready to merge from my POV. @carlisia do you want to do another pass? |
I'm gonna go ahead and merge so we can get the patch release out. Can always follow up with additional PRs if necessary, |
Fixes #660
Signed-off-by: Nolan Brubaker [email protected]