@@ -5,9 +5,10 @@ import (
5
5
"fmt"
6
6
"io"
7
7
8
+ "k8s.io/api/rbac/v1beta1"
9
+ kerrs "k8s.io/apimachinery/pkg/api/errors"
8
10
"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"
11
12
rbacinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
12
13
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
13
14
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
39
40
40
41
No resources are mutated.` )
41
42
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" )}
43
45
)
44
46
45
47
type MigrateAuthorizationOptions struct {
@@ -54,6 +56,7 @@ func NewCmdMigrateAuthorization(name, fullName string, f *clientcmd.Factory, in
54
56
Out : out ,
55
57
ErrOut : errout ,
56
58
AllNamespaces : true ,
59
+ Confirm : true , // force our save function to always run (it is read only)
57
60
Include : []string {
58
61
"clusterroles.authorization.openshift.io" ,
59
62
"roles.authorization.openshift.io" ,
@@ -73,8 +76,6 @@ func NewCmdMigrateAuthorization(name, fullName string, f *clientcmd.Factory, in
73
76
},
74
77
Deprecated : fmt .Sprintf ("will not work against 3.7 servers" ),
75
78
}
76
- options .ResourceOptions .Bind (cmd )
77
-
78
79
return cmd
79
80
}
80
81
@@ -83,20 +84,40 @@ func (o *MigrateAuthorizationOptions) Complete(name string, f *clientcmd.Factory
83
84
return fmt .Errorf ("%s takes no positional arguments" , name )
84
85
}
85
86
87
+ o .ResourceOptions .SaveFn = o .checkParity
86
88
if err := o .ResourceOptions .Complete (f , c ); err != nil {
87
89
return err
88
90
}
89
91
90
- kclient , err := f .ClientSet ()
92
+ discovery , err := f .DiscoveryClient ()
91
93
if err != nil {
92
94
return err
93
95
}
94
96
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 {
96
103
return err
97
104
}
98
105
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
100
121
101
122
return nil
102
123
}
@@ -106,130 +127,159 @@ func (o MigrateAuthorizationOptions) Validate() error {
106
127
}
107
128
108
129
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 )
112
132
}
113
133
114
134
// checkParity confirms that Openshift authorization objects are in sync with Kubernetes RBAC
115
135
// 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 ) {
119
140
case * authorizationapi.ClusterRole :
120
- errlist = append ( errlist , o .checkClusterRole (t ) ... )
141
+ err = o .checkClusterRole (t )
121
142
case * authorizationapi.Role :
122
- errlist = append ( errlist , o .checkRole (t ) ... )
143
+ err = o .checkRole (t )
123
144
case * authorizationapi.ClusterRoleBinding :
124
- errlist = append ( errlist , o .checkClusterRoleBinding (t ) ... )
145
+ err = o .checkClusterRoleBinding (t )
125
146
case * authorizationapi.RoleBinding :
126
- errlist = append ( errlist , o .checkRoleBinding (t ) ... )
147
+ err = o .checkRoleBinding (t )
127
148
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.
129
173
}
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
131
178
}
132
179
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
+ }
135
190
191
+ func (o * MigrateAuthorizationOptions ) checkClusterRole (originClusterRole * authorizationapi.ClusterRole ) migrate.TemporaryError {
136
192
// convert the origin role to a rbac role
137
193
convertedClusterRole , err := util .ConvertToRBACClusterRole (originClusterRole )
138
194
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 }
140
197
}
141
198
142
199
// try to get the equivalent rbac role from the api
143
200
rbacClusterRole , err := o .rbac .ClusterRoles ().Get (originClusterRole .Name , v1.GetOptions {})
144
201
if err != nil {
145
- errlist = append (errlist , err )
202
+ // it is possible that the controller has not synced this yet
203
+ return handleRBACGetError (err )
146
204
}
147
205
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
154
210
}
155
211
156
- return errlist
212
+ return nil
157
213
}
158
214
159
- func (o * MigrateAuthorizationOptions ) checkRole (originRole * authorizationapi.Role ) []error {
160
- var errlist []error
161
-
215
+ func (o * MigrateAuthorizationOptions ) checkRole (originRole * authorizationapi.Role ) migrate.TemporaryError {
162
216
// convert the origin role to a rbac role
163
217
convertedRole , err := util .ConvertToRBACRole (originRole )
164
218
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 }
166
221
}
167
222
168
223
// try to get the equivalent rbac role from the api
169
224
rbacRole , err := o .rbac .Roles (originRole .Namespace ).Get (originRole .Name , v1.GetOptions {})
170
225
if err != nil {
171
- errlist = append (errlist , err )
226
+ // it is possible that the controller has not synced this yet
227
+ return handleRBACGetError (err )
172
228
}
173
229
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
180
234
}
181
235
182
- return errlist
236
+ return nil
183
237
}
184
238
185
- func (o * MigrateAuthorizationOptions ) checkClusterRoleBinding (originRoleBinding * authorizationapi.ClusterRoleBinding ) []error {
186
- var errlist []error
187
-
239
+ func (o * MigrateAuthorizationOptions ) checkClusterRoleBinding (originRoleBinding * authorizationapi.ClusterRoleBinding ) migrate.TemporaryError {
188
240
// convert the origin role binding to a rbac role binding
189
241
convertedRoleBinding , err := util .ConvertToRBACClusterRoleBinding (originRoleBinding )
190
242
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 }
192
245
}
193
246
194
247
// try to get the equivalent rbac role binding from the api
195
248
rbacRoleBinding , err := o .rbac .ClusterRoleBindings ().Get (originRoleBinding .Name , v1.GetOptions {})
196
249
if err != nil {
197
- errlist = append (errlist , err )
250
+ // it is possible that the controller has not synced this yet
251
+ return handleRBACGetError (err )
198
252
}
199
253
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
206
258
}
207
259
208
- return errlist
260
+ return nil
209
261
}
210
262
211
- func (o * MigrateAuthorizationOptions ) checkRoleBinding (originRoleBinding * authorizationapi.RoleBinding ) []error {
212
- var errlist []error
213
-
263
+ func (o * MigrateAuthorizationOptions ) checkRoleBinding (originRoleBinding * authorizationapi.RoleBinding ) migrate.TemporaryError {
214
264
// convert the origin role binding to a rbac role binding
215
265
convertedRoleBinding , err := util .ConvertToRBACRoleBinding (originRoleBinding )
216
266
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 }
218
269
}
219
270
220
271
// try to get the equivalent rbac role binding from the api
221
272
rbacRoleBinding , err := o .rbac .RoleBindings (originRoleBinding .Namespace ).Get (originRoleBinding .Name , v1.GetOptions {})
222
273
if err != nil {
223
- errlist = append (errlist , err )
274
+ // it is possible that the controller has not synced this yet
275
+ return handleRBACGetError (err )
224
276
}
225
277
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
232
282
}
233
283
234
- return errlist
284
+ return nil
235
285
}
0 commit comments