Skip to content

Commit 496c518

Browse files
committed
Fix issues with oc adm migrate authorization
This change handles a number of bugs with the migrate authorization command: 1. Rework MigrateAuthorizationOptions.checkParity to be a MigrateActionFunc instead of a MigrateVisitFunc. This allows us to take advantage of migrate's default retry handling. 2. Add proper retry logic to checkParity, and have all check* funcs return the appropriate TemporaryError based on the situation. This coupled with migrate's existing retry logic makes the command resilient against common errors such as the deletion of resources. 3. Remove the binding of the standard migrate flags from migrate authorization. This command supports no parameters, and exposing the standard migrate parameters allows the user to accidentally break how the command runs. 4. Fix GroupVersion constants used for discovery based gating. They were incorrectly set to the internal version instead of v1. This would cause the policy based gating to always think that the server did not support policy objects. 5. Force RBAC client to use v1beta1 since that is the only version supported by a 3.6 server. This allows you to use a 3.9 client against a 3.6 server. 6. Remove rate limiting from the RBAC client to fix BZ 1513139. Only a cluster admin can interact with RBAC resources on a 3.6 server, so this will quickly error out if run by a non-privileged user. Bug 1513139 Signed-off-by: Monis Khan <[email protected]>
1 parent ee37719 commit 496c518

File tree

4 files changed

+134
-78
lines changed

4 files changed

+134
-78
lines changed

pkg/oc/admin/migrate/authorization/authorization.go

Lines changed: 117 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
"fmt"
66
"io"
77

8+
"k8s.io/api/rbac/v1beta1"
9+
kerrs "k8s.io/apimachinery/pkg/api/errors"
810
"k8s.io/apimachinery/pkg/apis/meta/v1"
9-
"k8s.io/apimachinery/pkg/runtime"
10-
utilerrors "k8s.io/apimachinery/pkg/util/errors"
11+
"k8s.io/client-go/util/flowcontrol"
1112
rbacinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
1213
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
1314
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@@ -39,7 +40,8 @@ var (
3940
4041
No resources are mutated.`)
4142

42-
errOutOfSync = errors.New("is not in sync with RBAC")
43+
// errOutOfSync is retriable since it could be caused by the controller lagging behind
44+
errOutOfSync = migrate.ErrRetriable{errors.New("is not in sync with RBAC")}
4345
)
4446

4547
type MigrateAuthorizationOptions struct {
@@ -54,6 +56,7 @@ func NewCmdMigrateAuthorization(name, fullName string, f *clientcmd.Factory, in
5456
Out: out,
5557
ErrOut: errout,
5658
AllNamespaces: true,
59+
Confirm: true, // force our save function to always run (it is read only)
5760
Include: []string{
5861
"clusterroles.authorization.openshift.io",
5962
"roles.authorization.openshift.io",
@@ -73,8 +76,6 @@ func NewCmdMigrateAuthorization(name, fullName string, f *clientcmd.Factory, in
7376
},
7477
Deprecated: fmt.Sprintf("will not work against 3.7 servers"),
7578
}
76-
options.ResourceOptions.Bind(cmd)
77-
7879
return cmd
7980
}
8081

@@ -83,20 +84,40 @@ func (o *MigrateAuthorizationOptions) Complete(name string, f *clientcmd.Factory
8384
return fmt.Errorf("%s takes no positional arguments", name)
8485
}
8586

87+
o.ResourceOptions.SaveFn = o.checkParity
8688
if err := o.ResourceOptions.Complete(f, c); err != nil {
8789
return err
8890
}
8991

90-
kclient, err := f.ClientSet()
92+
discovery, err := f.DiscoveryClient()
9193
if err != nil {
9294
return err
9395
}
9496

95-
if err := clientcmd.LegacyPolicyResourceGate(kclient.Discovery()); err != nil {
97+
if err := clientcmd.LegacyPolicyResourceGate(discovery); err != nil {
98+
return err
99+
}
100+
101+
config, err := f.ClientConfig()
102+
if err != nil {
96103
return err
97104
}
98105

99-
o.rbac = kclient.Rbac()
106+
// do not rate limit this client because it has to scan all RBAC data across the cluster
107+
// this is safe because only a cluster admin will have the ability to read these objects
108+
configShallowCopy := *config
109+
configShallowCopy.RateLimiter = flowcontrol.NewFakeAlwaysRateLimiter()
110+
111+
// This command is only compatible with a 3.6 server, which only supported RBAC v1beta1
112+
// Thus we must force that GV otherwise the client will default to v1
113+
gv := v1beta1.SchemeGroupVersion
114+
configShallowCopy.GroupVersion = &gv
115+
116+
rbac, err := rbacinternalversion.NewForConfig(&configShallowCopy)
117+
if err != nil {
118+
return err
119+
}
120+
o.rbac = rbac
100121

101122
return nil
102123
}
@@ -106,130 +127,159 @@ func (o MigrateAuthorizationOptions) Validate() error {
106127
}
107128

108129
func (o MigrateAuthorizationOptions) Run() error {
109-
return o.ResourceOptions.Visitor().Visit(func(info *resource.Info) (migrate.Reporter, error) {
110-
return o.checkParity(info.Object)
111-
})
130+
// we lie and say this object has changed so our save function will run
131+
return o.ResourceOptions.Visitor().Visit(migrate.AlwaysRequiresMigration)
112132
}
113133

114134
// checkParity confirms that Openshift authorization objects are in sync with Kubernetes RBAC
115135
// and returns an error if they are out of sync or if it encounters a conversion error
116-
func (o *MigrateAuthorizationOptions) checkParity(obj runtime.Object) (migrate.Reporter, error) {
117-
var errlist []error
118-
switch t := obj.(type) {
136+
func (o *MigrateAuthorizationOptions) checkParity(info *resource.Info, _ migrate.Reporter) error {
137+
var err migrate.TemporaryError
138+
139+
switch t := info.Object.(type) {
119140
case *authorizationapi.ClusterRole:
120-
errlist = append(errlist, o.checkClusterRole(t)...)
141+
err = o.checkClusterRole(t)
121142
case *authorizationapi.Role:
122-
errlist = append(errlist, o.checkRole(t)...)
143+
err = o.checkRole(t)
123144
case *authorizationapi.ClusterRoleBinding:
124-
errlist = append(errlist, o.checkClusterRoleBinding(t)...)
145+
err = o.checkClusterRoleBinding(t)
125146
case *authorizationapi.RoleBinding:
126-
errlist = append(errlist, o.checkRoleBinding(t)...)
147+
err = o.checkRoleBinding(t)
127148
default:
128-
return nil, nil // indicate that we ignored the object
149+
// this should never happen unless o.Include or the server is broken
150+
return fmt.Errorf("impossible type %T for checkParity info=%#v object=%#v", t, info, t)
151+
}
152+
153+
// We encountered no error, so this object is in sync.
154+
if err == nil {
155+
// we only perform read operations so we return this error to signal that we did not change anything
156+
return migrate.ErrUnchanged
157+
}
158+
159+
// At this point we know that we have some non-nil TemporaryError.
160+
// If it has the possibility of being transient, we need to sync ourselves with the current state of the object.
161+
if err.Temporary() {
162+
// The most likely cause is that an authorization object was deleted after we initially fetched it,
163+
// and the controller deleted the associated RBAC object, which caused our RBAC GET to fail.
164+
// We can confirm this by refetching the authorization object.
165+
refreshErr := info.Get()
166+
if refreshErr != nil {
167+
// Our refresh GET for this authorization object failed.
168+
// The default logic for migration errors is appropriate in this case (refreshErr is most likely a NotFound).
169+
return migrate.DefaultRetriable(info, refreshErr)
170+
}
171+
// We had no refreshErr, but encountered some other possibly transient error.
172+
// No special handling is required in this case, we just pass it through below.
129173
}
130-
return migrate.NotChanged, utilerrors.NewAggregate(errlist) // we only perform read operations
174+
175+
// All of the check* funcs return an appropriate TemporaryError based on the failure,
176+
// so we can pass that through to the default migration logic which will retry as needed.
177+
return err
131178
}
132179

133-
func (o *MigrateAuthorizationOptions) checkClusterRole(originClusterRole *authorizationapi.ClusterRole) []error {
134-
var errlist []error
180+
// handleRBACGetError signals for a retry on NotFound (handles deletion and sync lag)
181+
// and ServerTimeout (handles heavy load against the server).
182+
func handleRBACGetError(err error) migrate.TemporaryError {
183+
switch {
184+
case kerrs.IsNotFound(err), kerrs.IsServerTimeout(err):
185+
return migrate.ErrRetriable{err}
186+
default:
187+
return migrate.ErrNotRetriable{err}
188+
}
189+
}
135190

191+
func (o *MigrateAuthorizationOptions) checkClusterRole(originClusterRole *authorizationapi.ClusterRole) migrate.TemporaryError {
136192
// convert the origin role to a rbac role
137193
convertedClusterRole, err := util.ConvertToRBACClusterRole(originClusterRole)
138194
if err != nil {
139-
errlist = append(errlist, err)
195+
// conversion errors should basically never happen, so we do not attempt to retry on those
196+
return migrate.ErrNotRetriable{err}
140197
}
141198

142199
// try to get the equivalent rbac role from the api
143200
rbacClusterRole, err := o.rbac.ClusterRoles().Get(originClusterRole.Name, v1.GetOptions{})
144201
if err != nil {
145-
errlist = append(errlist, err)
202+
// it is possible that the controller has not synced this yet
203+
return handleRBACGetError(err)
146204
}
147205

148-
// compare the results if there have been no errors so far
149-
if len(errlist) == 0 {
150-
// if they are not equal, something has gone wrong and the two objects are not in sync
151-
if util.PrepareForUpdateClusterRole(convertedClusterRole, rbacClusterRole) {
152-
errlist = append(errlist, errOutOfSync)
153-
}
206+
// if they are not equal, something has gone wrong and the two objects are not in sync
207+
if util.PrepareForUpdateClusterRole(convertedClusterRole, rbacClusterRole) {
208+
// we retry on this since it could be caused by the controller lagging behind
209+
return errOutOfSync
154210
}
155211

156-
return errlist
212+
return nil
157213
}
158214

159-
func (o *MigrateAuthorizationOptions) checkRole(originRole *authorizationapi.Role) []error {
160-
var errlist []error
161-
215+
func (o *MigrateAuthorizationOptions) checkRole(originRole *authorizationapi.Role) migrate.TemporaryError {
162216
// convert the origin role to a rbac role
163217
convertedRole, err := util.ConvertToRBACRole(originRole)
164218
if err != nil {
165-
errlist = append(errlist, err)
219+
// conversion errors should basically never happen, so we do not attempt to retry on those
220+
return migrate.ErrNotRetriable{err}
166221
}
167222

168223
// try to get the equivalent rbac role from the api
169224
rbacRole, err := o.rbac.Roles(originRole.Namespace).Get(originRole.Name, v1.GetOptions{})
170225
if err != nil {
171-
errlist = append(errlist, err)
226+
// it is possible that the controller has not synced this yet
227+
return handleRBACGetError(err)
172228
}
173229

174-
// compare the results if there have been no errors so far
175-
if len(errlist) == 0 {
176-
// if they are not equal, something has gone wrong and the two objects are not in sync
177-
if util.PrepareForUpdateRole(convertedRole, rbacRole) {
178-
errlist = append(errlist, errOutOfSync)
179-
}
230+
// if they are not equal, something has gone wrong and the two objects are not in sync
231+
if util.PrepareForUpdateRole(convertedRole, rbacRole) {
232+
// we retry on this since it could be caused by the controller lagging behind
233+
return errOutOfSync
180234
}
181235

182-
return errlist
236+
return nil
183237
}
184238

185-
func (o *MigrateAuthorizationOptions) checkClusterRoleBinding(originRoleBinding *authorizationapi.ClusterRoleBinding) []error {
186-
var errlist []error
187-
239+
func (o *MigrateAuthorizationOptions) checkClusterRoleBinding(originRoleBinding *authorizationapi.ClusterRoleBinding) migrate.TemporaryError {
188240
// convert the origin role binding to a rbac role binding
189241
convertedRoleBinding, err := util.ConvertToRBACClusterRoleBinding(originRoleBinding)
190242
if err != nil {
191-
errlist = append(errlist, err)
243+
// conversion errors should basically never happen, so we do not attempt to retry on those
244+
return migrate.ErrNotRetriable{err}
192245
}
193246

194247
// try to get the equivalent rbac role binding from the api
195248
rbacRoleBinding, err := o.rbac.ClusterRoleBindings().Get(originRoleBinding.Name, v1.GetOptions{})
196249
if err != nil {
197-
errlist = append(errlist, err)
250+
// it is possible that the controller has not synced this yet
251+
return handleRBACGetError(err)
198252
}
199253

200-
// compare the results if there have been no errors so far
201-
if len(errlist) == 0 {
202-
// if they are not equal, something has gone wrong and the two objects are not in sync
203-
if util.PrepareForUpdateClusterRoleBinding(convertedRoleBinding, rbacRoleBinding) {
204-
errlist = append(errlist, errOutOfSync)
205-
}
254+
// if they are not equal, something has gone wrong and the two objects are not in sync
255+
if util.PrepareForUpdateClusterRoleBinding(convertedRoleBinding, rbacRoleBinding) {
256+
// we retry on this since it could be caused by the controller lagging behind
257+
return errOutOfSync
206258
}
207259

208-
return errlist
260+
return nil
209261
}
210262

211-
func (o *MigrateAuthorizationOptions) checkRoleBinding(originRoleBinding *authorizationapi.RoleBinding) []error {
212-
var errlist []error
213-
263+
func (o *MigrateAuthorizationOptions) checkRoleBinding(originRoleBinding *authorizationapi.RoleBinding) migrate.TemporaryError {
214264
// convert the origin role binding to a rbac role binding
215265
convertedRoleBinding, err := util.ConvertToRBACRoleBinding(originRoleBinding)
216266
if err != nil {
217-
errlist = append(errlist, err)
267+
// conversion errors should basically never happen, so we do not attempt to retry on those
268+
return migrate.ErrNotRetriable{err}
218269
}
219270

220271
// try to get the equivalent rbac role binding from the api
221272
rbacRoleBinding, err := o.rbac.RoleBindings(originRoleBinding.Namespace).Get(originRoleBinding.Name, v1.GetOptions{})
222273
if err != nil {
223-
errlist = append(errlist, err)
274+
// it is possible that the controller has not synced this yet
275+
return handleRBACGetError(err)
224276
}
225277

226-
// compare the results if there have been no errors so far
227-
if len(errlist) == 0 {
228-
// if they are not equal, something has gone wrong and the two objects are not in sync
229-
if util.PrepareForUpdateRoleBinding(convertedRoleBinding, rbacRoleBinding) {
230-
errlist = append(errlist, errOutOfSync)
231-
}
278+
// if they are not equal, something has gone wrong and the two objects are not in sync
279+
if util.PrepareForUpdateRoleBinding(convertedRoleBinding, rbacRoleBinding) {
280+
// we retry on this since it could be caused by the controller lagging behind
281+
return errOutOfSync
232282
}
233283

234-
return errlist
284+
return nil
235285
}

pkg/oc/admin/migrate/migrator.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ func timeStampNow() string {
5454
return time.Now().Format("0102 15:04:05.000000")
5555
}
5656

57-
// NotChanged is a Reporter returned by operations that are guaranteed to be read-only
58-
var NotChanged = ReporterBool(false)
59-
6057
// ResourceOptions assists in performing migrations on any object that
6158
// can be retrieved via the API.
6259
type ResourceOptions struct {
@@ -102,7 +99,12 @@ func (o *ResourceOptions) Bind(c *cobra.Command) {
10299
}
103100

104101
func (o *ResourceOptions) Complete(f *clientcmd.Factory, c *cobra.Command) error {
105-
o.Output = kcmdutil.GetFlagString(c, "output")
102+
// oc adm migrate authorization does not support printing and takes no parameters, so we ignore the error here
103+
var flagErr error
104+
o.Output, flagErr = c.Flags().GetString("output")
105+
if flagErr != nil {
106+
glog.V(4).Infof("Error getting output flag: %v", flagErr)
107+
}
106108
switch {
107109
case len(o.Output) > 0:
108110
printer, err := f.PrinterForOptions(kcmdutil.ExtractCmdPrintOptions(c, false))
@@ -362,23 +364,27 @@ var ErrUnchanged = fmt.Errorf("migration was not necessary")
362364
// both status and spec must be changed).
363365
var ErrRecalculate = fmt.Errorf("recalculate migration")
364366

367+
// MigrateError is an exported alias to error to allow external packages to use ErrRetriable and ErrNotRetriable
368+
type MigrateError error
369+
365370
// ErrRetriable is a wrapper for an error that a migrator may use to indicate the
366371
// specific error can be retried.
367372
type ErrRetriable struct {
368-
error
373+
MigrateError
369374
}
370375

371376
func (ErrRetriable) Temporary() bool { return true }
372377

373378
// ErrNotRetriable is a wrapper for an error that a migrator may use to indicate the
374379
// specific error cannot be retried.
375380
type ErrNotRetriable struct {
376-
error
381+
MigrateError
377382
}
378383

379384
func (ErrNotRetriable) Temporary() bool { return false }
380385

381-
type temporary interface {
386+
type TemporaryError interface {
387+
error
382388
// Temporary should return true if this is a temporary error
383389
Temporary() bool
384390
}
@@ -490,7 +496,7 @@ func (t *migrateTracker) try(info *resource.Info, retries int) (attemptResult, e
490496

491497
// canRetry returns true if the provided error indicates a retry is possible.
492498
func canRetry(err error) bool {
493-
if temp, ok := err.(temporary); ok && temp.Temporary() {
499+
if temp, ok := err.(TemporaryError); ok && temp.Temporary() {
494500
return true
495501
}
496502
return err == ErrRecalculate

pkg/oc/cli/cmd/create/policy_binding.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ func (o *CreatePolicyBindingOptions) Complete(cmd *cobra.Command, f *clientcmd.F
7272
}
7373
o.BindingNamespace = namespace
7474

75-
kclient, err := f.ClientSet()
75+
discovery, err := f.DiscoveryClient()
7676
if err != nil {
7777
return err
7878
}
7979

80-
if err := clientcmd.LegacyPolicyResourceGate(kclient.Discovery()); err != nil {
80+
if err := clientcmd.LegacyPolicyResourceGate(discovery); err != nil {
8181
return err
8282
}
8383
client, err := f.OpenshiftInternalAuthorizationClient()

pkg/oc/cli/util/clientcmd/gating.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"k8s.io/apimachinery/pkg/runtime/schema"
99
"k8s.io/client-go/discovery"
1010

11-
"github.com/openshift/origin/pkg/authorization/apis/authorization"
11+
authorization "github.com/openshift/api/authorization/v1"
1212
)
1313

1414
// LegacyPolicyResourceGate returns err if the server does not support the set of legacy policy objects (< 3.7)

0 commit comments

Comments
 (0)